home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / components / nsExtensionManager.js < prev    next >
Encoding:
Text File  |  2007-11-12  |  267.5 KB  |  7,206 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Extension Manager.
  16.  *
  17.  * The Initial Developer of the Original Code is Ben Goodger.
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Google Inc.)
  23.  *  Benjamin Smedberg <benjamin@smedbergs.us>
  24.  *  Jens Bannmann <jens.b@web.de>
  25.  *  Robert Strong <robert.bugzilla@gmail.com>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. //
  42. // TODO:
  43. // - better logging
  44. //
  45.  
  46. const nsIExtensionManager             = Components.interfaces.nsIExtensionManager;
  47. const nsIAddonUpdateCheckListener     = Components.interfaces.nsIAddonUpdateCheckListener;
  48. const nsIUpdateItem                   = Components.interfaces.nsIUpdateItem;
  49. const nsILocalFile                    = Components.interfaces.nsILocalFile;
  50. const nsILineInputStream              = Components.interfaces.nsILineInputStream;
  51. const nsIInstallLocation              = Components.interfaces.nsIInstallLocation;
  52. const nsIURL                          = Components.interfaces.nsIURL
  53. // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  54. // it has been removed will cause a crash on Mac OS X - bug 292823
  55. const nsIDirectoryEnumerator          = Components.interfaces.nsIDirectoryEnumerator;
  56.  
  57. const PREF_EM_APP_EXTENSIONS_VERSION  = "app.extensions.version";
  58. const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
  59. const PREF_UPDATE_COUNT               = "extensions.update.count";
  60. const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
  61. const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
  62. const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
  63. const PREF_EM_LAST_SELECTED_SKIN      = "extensions.lastSelectedSkin";
  64. const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
  65. const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
  66. const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
  67. const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
  68. const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
  69. const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
  70. const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
  71. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
  72. const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
  73. const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
  74. const PREF_XPINSTALL_STATUS_DLG_SKIN  = "xpinstall.dialog.progress.skin";
  75.  
  76. const DIR_EXTENSIONS                  = "extensions";
  77. const DIR_CHROME                      = "chrome";
  78. const DIR_STAGE                       = "staged-xpis";
  79. const FILE_EXTENSIONS                 = "extensions.rdf";
  80. const FILE_EXTENSION_MANIFEST         = "extensions.ini";
  81. const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
  82. const FILE_AUTOREG                    = ".autoreg";
  83. const FILE_INSTALL_MANIFEST           = "install.rdf";
  84. const FILE_CONTENTS_MANIFEST          = "contents.rdf";
  85. const FILE_CHROME_MANIFEST            = "chrome.manifest";
  86.  
  87. const UNKNOWN_XPCOM_ABI               = "unknownABI";
  88.  
  89. const FILE_LOGFILE                    = "extensionmanager.log";
  90.  
  91. const FILE_DEFAULT_THEME_JAR          = "classic.jar";
  92.  
  93. const KEY_PROFILEDIR                  = "ProfD";
  94. const KEY_PROFILEDS                   = "ProfDS";
  95. const KEY_APPDIR                      = "XCurProcD";
  96. const KEY_TEMPDIR                     = "TmpD";
  97.  
  98. const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
  99. const EM_ITEM_INSTALLED               = "item-installed";
  100. const EM_ITEM_UPGRADED                = "item-upgraded";
  101. const EM_ITEM_UNINSTALLED             = "item-uninstalled";
  102. const EM_ITEM_ENABLED                 = "item-enabled";
  103. const EM_ITEM_DISABLED                = "item-disabled";
  104. const EM_ITEM_CANCEL                  = "item-cancel-action";
  105.  
  106. const OP_NONE                         = "";
  107. const OP_NEEDS_INSTALL                = "needs-install";
  108. const OP_NEEDS_UPGRADE                = "needs-upgrade";
  109. const OP_NEEDS_UNINSTALL              = "needs-uninstall";
  110. const OP_NEEDS_ENABLE                 = "needs-enable";
  111. const OP_NEEDS_DISABLE                = "needs-disable";
  112.  
  113. const KEY_APP_PROFILE                 = "app-profile";
  114. const KEY_APP_GLOBAL                  = "app-global";
  115.  
  116. const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
  117.  
  118. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  119. const PREFIX_NS_CHROME                = "http://www.mozilla.org/rdf/chrome#";
  120. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  121. const PREFIX_EXTENSION                = "urn:mozilla:extension:";
  122. const PREFIX_THEME                    = "urn:mozilla:theme:";
  123. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  124. const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
  125. const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}";
  126. const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
  127.  
  128. const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
  129. const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
  130. const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  131. const URI_FINALIZE_DIALOG             = "chrome://mozapps/content/extensions/finalize.xul";
  132. const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
  133. const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
  134. const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
  135. const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
  136.  
  137. const INSTALLERROR_SUCCESS               = 0;
  138. const INSTALLERROR_INVALID_VERSION       = -1;
  139. const INSTALLERROR_INVALID_GUID          = -2;
  140. const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
  141. const INSTALLERROR_PHONED_HOME           = -4;
  142. const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
  143.  
  144. const MODE_WRONLY   = 0x02;
  145. const MODE_CREATE   = 0x08;
  146. const MODE_APPEND   = 0x10;
  147. const MODE_TRUNCATE = 0x20;
  148.  
  149. const PERMS_FILE      = 0644;
  150. const PERMS_DIRECTORY = 0755;
  151.  
  152. var gApp  = null;
  153. var gPref = null;
  154. var gRDF  = null;
  155. var gOS   = null;
  156. var gXPCOMABI             = null;
  157. var gOSTarget             = null;
  158. var gConsole              = null;
  159. var gInstallManifestRoot  = null;
  160. var gVersionChecker       = null;
  161. var gLoggingEnabled       = null;
  162.  
  163. /** 
  164.  * Valid GUIDs fit this pattern.
  165.  */
  166. var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
  167.  
  168. /**
  169.  * Creates a Version Checker object.
  170.  * @returns A handle to the global Version Checker service.
  171.  */
  172. function getVersionChecker() {
  173.   if (!gVersionChecker) {
  174.     gVersionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
  175.                                 .getService(Components.interfaces.nsIVersionComparator);
  176.   }
  177.   return gVersionChecker;
  178. }
  179.  
  180. var BundleManager = { 
  181.   /**
  182.   * Creates and returns a String Bundle at the specified URI
  183.   * @param   bundleURI
  184.   *          The URI of the bundle to load
  185.   * @returns A nsIStringBundle which was retrieved.
  186.   */
  187.   getBundle: function(bundleURI) {
  188.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  189.                         .getService(Components.interfaces.nsIStringBundleService);
  190.     return sbs.createBundle(bundleURI);
  191.   },
  192.   
  193.   _appName: "",
  194.   
  195.   /**
  196.    * The Application's display name.
  197.    */
  198.   get appName() {
  199.     if (!this._appName) {
  200.       var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
  201.       this._appName = brandBundle.GetStringFromName("brandShortName");
  202.     }
  203.     return this._appName;
  204.   }
  205. };
  206.  
  207. ///////////////////////////////////////////////////////////////////////////////
  208. //
  209. // Utility Functions
  210. //
  211. function EM_NS(property) {
  212.   return PREFIX_NS_EM + property;
  213. }
  214.  
  215. function CHROME_NS(property) {
  216.   return PREFIX_NS_CHROME + property;
  217. }
  218.  
  219. function EM_R(property) {
  220.   return gRDF.GetResource(EM_NS(property));
  221. }
  222.  
  223. function EM_L(literal) {
  224.   return gRDF.GetLiteral(literal);
  225. }
  226.  
  227. function EM_I(integer) {
  228.   return gRDF.GetIntLiteral(integer);
  229. }
  230.  
  231. /**
  232.  * Gets a preference value, handling the case where there is no default.
  233.  * @param   func
  234.  *          The name of the preference function to call, on nsIPrefBranch
  235.  * @param   preference
  236.  *          The name of the preference
  237.  * @param   defaultValue
  238.  *          The default value to return in the event the preference has 
  239.  *          no setting
  240.  * @returns The value of the preference, or undefined if there was no
  241.  *          user or default value.
  242.  */
  243. function getPref(func, preference, defaultValue) {
  244.   try {
  245.     return gPref[func](preference);
  246.   }
  247.   catch (e) {
  248.   }
  249.   return defaultValue;
  250. }
  251.  
  252. /**
  253.  * Initializes a RDF Container at a URI in a datasource.
  254.  * @param   datasource
  255.  *          The datasource the container is in
  256.  * @param   root
  257.  *          The RDF Resource which is the root of the container.
  258.  * @returns The nsIRDFContainer, initialized at the root.
  259.  */
  260. function getContainer(datasource, root) {
  261.   var ctr = Components.classes["@mozilla.org/rdf/container;1"]
  262.                       .createInstance(Components.interfaces.nsIRDFContainer);
  263.   ctr.Init(datasource, root);
  264.   return ctr;
  265. }
  266.  
  267. /**
  268.  * Gets a RDF Resource for item with the given ID
  269.  * @param   id
  270.  *          The GUID of the item to construct a RDF resource to the 
  271.  *          active item for
  272.  * @returns The RDF Resource to the Active item. 
  273.  */
  274. function getResourceForID(id) {
  275.   return gRDF.GetResource(PREFIX_ITEM_URI + id);
  276. }
  277.  
  278. /**
  279.  * Construct a nsIUpdateItem with the supplied metadata
  280.  * ...
  281.  */
  282. function makeItem(id, version, locationKey, minVersion, maxVersion, name, 
  283.                   updateURL, updateHash, iconURL, updateRDF, type) {
  284.   var item = Components.classes["@mozilla.org/updates/item;1"]
  285.                        .createInstance(Components.interfaces.nsIUpdateItem);
  286.   item.init(id, version, locationKey, minVersion, maxVersion, name,
  287.             updateURL, updateHash, iconURL, updateRDF, type);
  288.   return item;
  289. }
  290.  
  291. /**
  292.  * Gets the specified directory at the speciifed hierarchy under a 
  293.  * Directory Service key. 
  294.  * @param   key
  295.  *          The Directory Service Key to start from
  296.  * @param   pathArray
  297.  *          An array of path components to locate beneath the directory 
  298.  *          specified by |key|
  299.  * @return  nsIFile object for the location specified. If the directory
  300.  *          requested does not exist, it is created, along with any
  301.  *          parent directories that need to be created.
  302.  */
  303. function getDir(key, pathArray) {
  304.   return getDirInternal(key, pathArray, true);
  305. }
  306.  
  307. /**
  308.  * Gets the specified directory at the speciifed hierarchy under a 
  309.  * Directory Service key. 
  310.  * @param   key
  311.  *          The Directory Service Key to start from
  312.  * @param   pathArray
  313.  *          An array of path components to locate beneath the directory 
  314.  *          specified by |key|
  315.  * @return  nsIFile object for the location specified. If the directory
  316.  *          requested does not exist, it is NOT created.
  317.  */
  318. function getDirNoCreate(key, pathArray) {
  319.   return getDirInternal(key, pathArray, false);
  320. }
  321.  
  322. /**
  323.  * Gets the specified directory at the speciifed hierarchy under a 
  324.  * Directory Service key. 
  325.  * @param   key
  326.  *          The Directory Service Key to start from
  327.  * @param   pathArray
  328.  *          An array of path components to locate beneath the directory 
  329.  *          specified by |key|
  330.  * @param   shouldCreate
  331.  *          true if the directory hierarchy specified in |pathArray|
  332.  *          should be created if it does not exist,
  333.  *          false otherwise.
  334.  * @return  nsIFile object for the location specified. 
  335.  */
  336. function getDirInternal(key, pathArray, shouldCreate) {
  337.   var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
  338.                               .getService(Components.interfaces.nsIProperties);
  339.   var dir = fileLocator.get(key, nsILocalFile);
  340.   for (var i = 0; i < pathArray.length; ++i) {
  341.     dir.append(pathArray[i]);
  342.     if (shouldCreate && !dir.exists())
  343.       dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  344.   }
  345.   dir.followLinks = false;
  346.   return dir;
  347. }
  348.  
  349. /**
  350.  * Gets the file at the speciifed hierarchy under a Directory Service key.
  351.  * @param   key
  352.  *          The Directory Service Key to start from
  353.  * @param   pathArray
  354.  *          An array of path components to locate beneath the directory 
  355.  *          specified by |key|. The last item in this array must be the
  356.  *          leaf name of a file.
  357.  * @return  nsIFile object for the file specified. The file is NOT created
  358.  *          if it does not exist, however all required directories along 
  359.  *          the way are.
  360.  */
  361. function getFile(key, pathArray) {
  362.   var file = getDir(key, pathArray.slice(0, -1));
  363.   file.append(pathArray[pathArray.length - 1]);
  364.   return file;
  365. }
  366.  
  367. /**
  368.  * Gets the descriptor of a directory as a relative path to common base
  369.  * directories (profile, user home, app install dir, etc).
  370.  *
  371.  * @param   itemLocation
  372.  *          The nsILocalFile representing the item's directory.
  373.  * @param   installLocation the nsIInstallLocation for this item
  374.  */
  375. function getDescriptorFromFile(itemLocation, installLocation) {
  376.   var baseDir = installLocation.location;
  377.  
  378.   if (baseDir && baseDir.contains(itemLocation, true)) {
  379.     return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
  380.   }
  381.  
  382.   return "abs%" + itemLocation.persistentDescriptor;
  383. }
  384.  
  385. function getAbsoluteDescriptor(itemLocation) {
  386.   return itemLocation.persistentDescriptor;
  387. }
  388.  
  389. /**
  390.  * Initializes a Local File object based on a descriptor
  391.  * provided by "getDescriptorFromFile".
  392.  *
  393.  * @param   descriptor
  394.  *          The descriptor that locates the directory
  395.  * @param   installLocation
  396.  *          The nsIInstallLocation object for this item.
  397.  * @returns The nsILocalFile object representing the location of the item
  398.  */
  399. function getFileFromDescriptor(descriptor, installLocation) {
  400.   var location = Components.classes["@mozilla.org/file/local;1"]
  401.                            .createInstance(nsILocalFile);
  402.  
  403.   var m = descriptor.match(/^(abs|rel)\%(.*)$/);
  404.   if (!m)
  405.     throw Components.results.NS_ERROR_INVALID_ARG;
  406.  
  407.   if (m[1] == "rel") {
  408.     location.setRelativeDescriptor(installLocation.location, m[2]);
  409.   }
  410.   else {
  411.     location.persistentDescriptor = m[2];
  412.   }
  413.  
  414.   return location;
  415. }
  416.  
  417. /**
  418.  * Determines if a file is an item package - either a XPI or a JAR file.
  419.  * @param   file
  420.  *          The file to check
  421.  * @returns true if the file is an item package, false otherwise.
  422.  */
  423. function fileIsItemPackage(file) {
  424.   var fileURL = getURIFromFile(file);
  425.   if (fileURL instanceof nsIURL)
  426.     var extension = fileURL.fileExtension.toLowerCase();
  427.   return extension == "xpi" || extension == "jar";
  428. }
  429.  
  430. /** 
  431.  * Return the leaf name used by the extension system for staging an item.
  432.  * @param   id
  433.  *          The GUID of the item
  434.  * @param   type
  435.  *          The nsIUpdateItem type of the item
  436.  * @returns The leaf name of the staged file.
  437.  */
  438. function getStagedLeafName(id, type) {
  439.   if (type == nsIUpdateItem.TYPE_THEME) 
  440.     return id + ".jar";
  441.   return id + ".xpi";
  442. }
  443.  
  444. /**
  445.  * Opens a safe file output stream for writing. 
  446.  * @param   file
  447.  *          The file to write to.
  448.  * @param   modeFlags
  449.  *          (optional) File open flags. Can be undefined. 
  450.  * @returns nsIFileOutputStream to write to.
  451.  */
  452. function openSafeFileOutputStream(file, modeFlags) {
  453.   var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
  454.                       .createInstance(Components.interfaces.nsIFileOutputStream);
  455.   if (modeFlags === undefined)
  456.     modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
  457.   if (!file.exists()) 
  458.     file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  459.   fos.init(file, modeFlags, PERMS_FILE, 0);
  460.   return fos;
  461. }
  462.  
  463. /**
  464.  * Closes a safe file output stream.
  465.  * @param   stream
  466.  *          The stream to close.
  467.  */
  468. function closeSafeFileOutputStream(stream) {
  469.   if (stream instanceof Components.interfaces.nsISafeOutputStream)
  470.     stream.finish();
  471.   else
  472.     stream.close();
  473. }
  474.  
  475. /**
  476.  * Deletes a directory and its children. First it tries nsIFile::Remove(true).
  477.  * If that fails it will fall back to recursing, setting the appropriate
  478.  * permissions, and deleting the current entry. This is needed for when we have
  479.  * rights to delete a directory but there are entries that have a read-only
  480.  * attribute (e.g. a copy restore from a read-only CD, etc.)
  481.  * @param   dir
  482.  *          A nsIFile for the directory to be deleted
  483.  */
  484. function removeDirRecursive(dir) {
  485.   try {
  486.     dir.remove(true);
  487.     return;
  488.   }
  489.   catch (e) {
  490.   }
  491.  
  492.   var dirEntries = dir.directoryEntries;
  493.   while (dirEntries.hasMoreElements()) {
  494.     var entry = dirEntries.getNext().QueryInterface(Components.interfaces.nsIFile);
  495.  
  496.     if (entry.isDirectory()) {
  497.       removeDirRecursive(entry);
  498.     }
  499.     else {
  500.       entry.permissions = PERMS_FILE;
  501.       entry.remove(false);
  502.     }
  503.   }
  504.   dir.permissions = PERMS_DIRECTORY;
  505.   dir.remove(true);
  506. }
  507.  
  508. /**
  509.  * Logs a string to the error console. 
  510.  * @param   string
  511.  *          The string to write to the error console..
  512.  */  
  513. function LOG(string) {
  514.   if (gLoggingEnabled)  
  515.     dump("*** " + string + "\n");
  516.   gConsole.logStringMessage(string);
  517. }
  518.  
  519. /** 
  520.  * Randomize the specified file name. Used to force RDF to bypass the cache
  521.  * when loading certain types of files.
  522.  * @param   fileName 
  523.  *          A file name to randomize, e.g. install.rdf
  524.  * @returns A randomized file name, e.g. install-xyz.rdf
  525.  */
  526. function getRandomFileName(fileName) {
  527.   var extensionDelimiter = fileName.lastIndexOf(".");
  528.   var prefix = fileName.substr(0, extensionDelimiter);
  529.   var suffix = fileName.substr(extensionDelimiter);
  530.   
  531.   var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
  532.   var nameString = prefix + "-";
  533.   for (var i = 0; i < 3; ++i) {
  534.     var index = Math.round((Math.random()) * characters.length);
  535.     nameString += characters.charAt(index);
  536.   }
  537.   return nameString + "." + suffix;
  538. }
  539.  
  540. /**
  541.  * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
  542.  * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource 
  543.  * are NOT prefixed.
  544.  * @param   type
  545.  *          The nsIUpdateItem type to find a RDF URI prefix for
  546.  * @returns The RDF URI prefix.
  547.  */
  548. function getItemPrefix(type) {
  549.   if (type & nsIUpdateItem.TYPE_EXTENSION) 
  550.     return PREFIX_EXTENSION;
  551.   else if (type & nsIUpdateItem.TYPE_THEME)
  552.     return PREFIX_THEME;
  553.   return PREFIX_ITEM_URI;
  554. }
  555.  
  556. /**
  557.  * Trims a prefix from a string.
  558.  * @param   string
  559.  *          The source string
  560.  * @param   prefix
  561.  *          The prefix to remove.
  562.  * @returns The suffix (string - prefix)
  563.  */
  564. function stripPrefix(string, prefix) {
  565.   return string.substr(prefix.length);
  566. }
  567.  
  568. /**
  569.  * Gets a File URL spec for a nsIFile
  570.  * @param   file
  571.  *          The file to get a file URL spec to
  572.  * @returns The file URL spec to the file
  573.  */
  574. function getURLSpecFromFile(file) {
  575.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  576.                          .getService(Components.interfaces.nsIIOService);
  577.   var fph = ioServ.getProtocolHandler("file")
  578.                   .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  579.   return fph.getURLSpecFromFile(file);
  580. }
  581.  
  582. /**
  583.  * Constructs a URI to a spec.
  584.  * @param   spec
  585.  *          The spec to construct a URI to
  586.  * @returns The nsIURI constructed.
  587.  */
  588. function newURI(spec) {
  589.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  590.                          .getService(Components.interfaces.nsIIOService);
  591.   return ioServ.newURI(spec, null, null);
  592. }
  593.  
  594. /** 
  595.  * Constructs a File URI to a nsIFile
  596.  * @param   file
  597.  *          The file to construct a File URI to
  598.  * @returns The file URI to the file
  599.  */
  600. function getURIFromFile(file) {
  601.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  602.                          .getService(Components.interfaces.nsIIOService);
  603.   return ioServ.newFileURI(file);
  604. }
  605.  
  606. /**
  607.  * @returns Whether or not we are currently running in safe mode.
  608.  */
  609. function inSafeMode() {
  610.   return gApp.inSafeMode;
  611. }
  612.  
  613. /**
  614.  * Extract the string value from a RDF Literal or Resource
  615.  * @param   literalOrResource
  616.  *          RDF String Literal or Resource
  617.  * @returns String value of the literal or resource, or undefined if the object
  618.  *          supplied is not a RDF string literal or resource.
  619.  */
  620. function stringData(literalOrResource) {
  621.   if (literalOrResource instanceof Components.interfaces.nsIRDFLiteral)
  622.     return literalOrResource.Value;
  623.   if (literalOrResource instanceof Components.interfaces.nsIRDFResource)
  624.     return literalOrResource.Value;
  625.   return undefined;
  626. }
  627.  
  628. /**
  629.  * Extract the integer value of a RDF Literal
  630.  * @param   literal
  631.  *          nsIRDFInt literal
  632.  * @return  integer value of the literal
  633.  */
  634. function intData(literal) {
  635.   if (literal instanceof Components.interfaces.nsIRDFInt)
  636.     return literal.Value;
  637.   return undefined;
  638. }
  639.  
  640. /**
  641.  * Gets a property from an install manifest.
  642.  * @param   installManifest
  643.  *          An Install Manifest datasource to read from
  644.  * @param   property
  645.  *          The name of a proprety to read (sans EM_NS)
  646.  * @returns The literal value of the property, or undefined if the property has
  647.  *          no value.
  648.  */
  649. function getManifestProperty(installManifest, property) {
  650.   var target = installManifest.GetTarget(gInstallManifestRoot, 
  651.                                          gRDF.GetResource(EM_NS(property)), true);
  652.   var val = stringData(target);
  653.   return val === undefined ? intData(target) : val;
  654. }
  655.  
  656. /**
  657.  * Given an Install Manifest Datasource, retrieves the type of item the manifest
  658.  * describes.
  659.  * @param   installManifest 
  660.  *          The Install Manifest Datasource.
  661.  * @return  The nsIUpdateItem type of the item described by the manifest
  662.  *          returns TYPE_EXTENSION if attempts to determine the type fail.
  663.  */
  664. function getAddonTypeFromInstallManifest(installManifest) {
  665.   var typeArc = gRDF.GetResource(EM_NS("type"));
  666.   var type = intData(installManifest.GetTarget(gInstallManifestRoot, typeArc, true));
  667.   if (type !== undefined)
  668.     return type;
  669.  
  670.   // Firefox 1.0 and earlier did not support addon-type annotation on the 
  671.   // Install Manifest, so we fall back to a theme-only property to 
  672.   // differentiate.
  673.   if (getManifestProperty(installManifest, "internalName") !== undefined)
  674.     return nsIUpdateItem.TYPE_THEME;
  675.  
  676.   // If no type is provided, default to "Extension"
  677.   return nsIUpdateItem.TYPE_EXTENSION;    
  678. }
  679.  
  680. /**
  681.  * Shows a message about an incompatible Extension/Theme. 
  682.  * @param   installData
  683.  *          An Install Data object from |getInstallData|
  684.  */
  685. function showIncompatibleError(installData) {
  686.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  687.   var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
  688.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  689.                                                     params, params.length);
  690.   var message;
  691.   var targetAppData = installData.currentApp;
  692.   if (!targetAppData) {
  693.     params = [installData.name, installData.version, BundleManager.appName];
  694.     message = extensionStrings.formatStringFromName("incompatibleMessageNoApp", 
  695.                                                     params, params.length);
  696.   }
  697.   else if (targetAppData.minVersion == targetAppData.maxVersion) {
  698.     // If the min target app version and the max target app version are the same, don't show
  699.     // a message like, "Foo is only compatible with Firefox versions 0.7 to 0.7", rather just
  700.     // show, "Foo is only compatible with Firefox 0.7"
  701.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  702.               installData.name, installData.version, BundleManager.appName, 
  703.               targetAppData.minVersion];
  704.     message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion", 
  705.                                                     params, params.length);
  706.   }
  707.   else {
  708.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  709.               installData.name, installData.version, BundleManager.appName, 
  710.               targetAppData.minVersion, targetAppData.maxVersion];
  711.     message = extensionStrings.formatStringFromName("incompatibleMsg", params, params.length);
  712.   }
  713.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  714.                      .getService(Components.interfaces.nsIPromptService);
  715.   ps.alert(null, title, message);
  716. }
  717.  
  718. /**
  719.  * Shows a message.
  720.  * @param   titleKey
  721.  *          String key of the title string in the Extensions localization file.
  722.  * @param   messageKey
  723.  *          String key of the message string in the Extensions localization file.
  724.  * @param   messageParams
  725.  *          Array of strings to be substituted into |messageKey|. Can be null.
  726.  */
  727. function showMessage(titleKey, titleParams, messageKey, messageParams) {
  728.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  729.   if (titleParams && titleParams.length > 0) {
  730.     var title = extensionStrings.formatStringFromName(titleKey, titleParams,
  731.                                                       titleParams.length);
  732.   }
  733.   else
  734.     title = extensionStrings.GetStringFromName(titleKey);
  735.  
  736.   if (messageParams && messageParams.length > 0) {
  737.     var message = extensionStrings.formatStringFromName(messageKey, messageParams,
  738.                                                         messageParams.length);
  739.   }
  740.   else
  741.     message = extensionStrings.GetStringFromName(messageKey);
  742.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  743.                      .getService(Components.interfaces.nsIPromptService);
  744.   ps.alert(null, title, message);
  745. }
  746.  
  747. /** 
  748.  * Gets a zip reader for the file specified.
  749.  * @param   zipFile
  750.  *          A ZIP archive to open with a nsIZipReader.
  751.  * @return  A nsIZipReader for the file specified.
  752.  */
  753. function getZipReaderForFile(zipFile) {
  754.   try {
  755.     var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  756.                               .createInstance(Components.interfaces.nsIZipReader);
  757.     zipReader.init(zipFile);
  758.     zipReader.open();
  759.   }
  760.   catch (e) {
  761.     zipReader.close();
  762.     throw e;
  763.   }
  764.   return zipReader;
  765. }
  766.  
  767. /** 
  768.  * Extract a RDF file from a ZIP archive to a random location in the system
  769.  * temp directory.
  770.  * @param   zipFile
  771.  *          A ZIP archive to read from
  772.  * @param   fileName 
  773.  *          The name of the file to read from the zip. 
  774.  * @param   suppressErrors
  775.  *          Whether or not to report errors. 
  776.  * @return  The file created in the temp directory.
  777.  */
  778. function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
  779.   var file = getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
  780.   try {
  781.     var zipReader = getZipReaderForFile(zipFile);
  782.     zipReader.getEntry(fileName);
  783.     zipReader.extract(fileName, file);
  784.     zipReader.close();
  785.   }
  786.   catch (e) {
  787.     if (!suppressErrors) {
  788.       showMessage("missingFileTitle", [], "missingFileMessage", 
  789.                   [BundleManager.appName, fileName]);
  790.       throw e;
  791.     }
  792.   }
  793.   return file;
  794. }
  795.  
  796. /**
  797.  * Show a message to the user informing them they are installing an old non-EM
  798.  * style Theme, and that these are not supported.
  799.  * @param   installManifest 
  800.  *          The Old-Style Contents Manifest datasource representing the theme. 
  801.  */
  802. function showOldThemeError(contentsManifest) {
  803.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  804.   var params = [extensionStrings.GetStringFromName("theme")];
  805.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  806.                                                     params, params.length);
  807.   var appVersion = extensionStrings.GetStringFromName("incompatibleOlder");
  808.   
  809.   try {  
  810.     var ctr = getContainer(contentsManifest, 
  811.                            gRDF.GetResource("urn:mozilla:skin:root"));
  812.     var elts = ctr.GetElements();
  813.     var nameArc = gRDF.GetResource(CHROME_NS("displayName"));
  814.     while (elts.hasMoreElements()) {
  815.       var elt = elts.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  816.       themeName = stringData(contentsManifest.GetTarget(elt, nameArc, true));
  817.       if (themeName) 
  818.         break;
  819.     }
  820.   }
  821.   catch (e) {
  822.     themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  823.   }
  824.   
  825.   params = [themeName, "", BundleManager.appName, gApp.version, themeName, "", 
  826.             BundleManager.appName, appVersion];
  827.   var message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion",
  828.                                                       params, params.length);
  829.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  830.                      .getService(Components.interfaces.nsIPromptService);
  831.   ps.alert(null, title, message);
  832. }
  833.  
  834. /**
  835.  * Gets an Install Manifest datasource from a file.
  836.  * @param   file
  837.  *          The nsIFile that contains the Install Manifest RDF
  838.  * @returns The Install Manifest datasource
  839.  */
  840. function getInstallManifest(file) {
  841.   var fileURL = getURLSpecFromFile(file);
  842.   var ds = gRDF.GetDataSourceBlocking(fileURL);
  843.   var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
  844.   if (!arcs.hasMoreElements()) {
  845.     ds = null;
  846.     var uri = Components.classes["@mozilla.org/network/io-service;1"]
  847.                         .getService(Components.interfaces.nsIIOService)
  848.                         .newFileURI(file);
  849.     var url = uri.QueryInterface(nsIURL);
  850.     showMessage("malformedTitle", [], "malformedMessage", 
  851.                 [BundleManager.appName, url.fileName]);
  852.   }
  853.   return ds;
  854. }
  855.  
  856. /**
  857.  * An enumeration of items in a JS array.
  858.  * @constructor
  859.  */
  860. function ArrayEnumerator(aItems) {
  861.   this._index = 0;
  862.   if (aItems) {
  863.     for (var i = 0; i < aItems.length; ++i) {
  864.       if (!aItems[i])
  865.         aItems.splice(i, 1);      
  866.     }
  867.   }
  868.   this._contents = aItems;
  869. }
  870.  
  871. ArrayEnumerator.prototype = {
  872.   _index: 0,
  873.   _contents: [],
  874.   
  875.   hasMoreElements: function() {
  876.     return this._index < this._contents.length;
  877.   },
  878.   
  879.   getNext: function() {
  880.     return this._contents[this._index++];      
  881.   }
  882. };
  883.  
  884. /**
  885.  * An enumeration of files in a JS array.
  886.  * @param   files
  887.  *          The files to enumerate
  888.  * @constructor
  889.  */
  890. function FileEnumerator(files) {
  891.   this._index = 0;
  892.   if (files) {
  893.     for (var i = 0; i < files.length; ++i) {
  894.       if (!files[i])
  895.         files.splice(i, 1);      
  896.     }
  897.   }
  898.   this._contents = files;
  899. }
  900.  
  901. FileEnumerator.prototype = {
  902.   _index: 0,
  903.   _contents: [],
  904.  
  905.   /**
  906.    * Gets the next file in the sequence.
  907.    */  
  908.   get nextFile() {
  909.     if (this._index < this._contents.length)
  910.       return this._contents[this._index++];
  911.     return null;
  912.   },
  913.   
  914.   /**
  915.    * Stop enumerating. Nothing to do here.
  916.    */
  917.   close: function() {
  918.   },
  919. };
  920.  
  921. /**
  922.  * An object which identifies an Install Location for items, where the location
  923.  * relationship is each item living in a directory named with its GUID under 
  924.  * the directory used when constructing this object.
  925.  *
  926.  * e.g. <location>\{GUID1}
  927.  *      <location>\{GUID2}
  928.  *      <location>\{GUID3}
  929.  *      ...
  930.  *
  931.  * @param   name
  932.  *          The string identifier of this Install Location.
  933.  * @param   location
  934.  *          The directory that contains the items. 
  935.  * @constructor
  936.  */
  937. function DirectoryInstallLocation(name, location, restricted, priority) {
  938.   this._name = name;
  939.   if (location.exists()) {
  940.     if (!location.isDirectory())
  941.       throw new Error("location must be a directoy!");
  942.   }
  943.   else {
  944.     try {
  945.       location.create(nsILocalFile.DIRECTORY_TYPE, 0775);
  946.     }
  947.     catch (e) {
  948.       LOG("DirectoryInstallLocation: failed to create location " + 
  949.           " directory = " + location.path + ", exception = " + e + "\n");
  950.     }
  951.   }
  952.  
  953.   this._location = location;
  954.   this._locationToIDMap = {};
  955.   this._restricted = restricted;
  956.   this._priority = priority;
  957. }
  958. DirectoryInstallLocation.prototype = {
  959.   _name           : "",
  960.   _location       : null,
  961.   _locationToIDMap: null,
  962.   _restricted     : false,
  963.   _priority       : 0,
  964.   _canAccess      : null,
  965.   
  966.   /**
  967.    * See nsIExtensionManager.idl
  968.    */
  969.   get name() {
  970.     return this._name;
  971.   },
  972.   
  973.   /**
  974.    * Reads a directory linked to in a file.
  975.    * @param   file
  976.    *          The file containing the directory path
  977.    * @returns A nsILocalFile object representing the linked directory.
  978.    */
  979.   _readDirectoryFromFile: function(file) {
  980.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  981.                         .createInstance(Components.interfaces.nsIFileInputStream);
  982.     fis.init(file, -1, -1, false);
  983.     var line = { value: "" };
  984.     if (fis instanceof nsILineInputStream)
  985.       fis.readLine(line);
  986.     fis.close();
  987.     if (line.value) {
  988.       var linkedDirectory = Components.classes["@mozilla.org/file/local;1"]
  989.                                       .createInstance(nsILocalFile);
  990.       try {
  991.         linkedDirectory.initWithPath(line.value);
  992.       }
  993.       catch (e) {
  994.         linkedDirectory.setRelativeDescriptor(file.parent, line.value);
  995.       }
  996.       
  997.       return linkedDirectory;
  998.     }
  999.     return null;
  1000.   },
  1001.   
  1002.   /**
  1003.    * See nsIExtensionManager.idl
  1004.    */
  1005.   get itemLocations() {
  1006.     var locations = [];
  1007.     if (!this._location.exists())
  1008.       return new FileEnumerator(locations);
  1009.     
  1010.     try {
  1011.       var entries = this._location.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1012.       while (true) {
  1013.         var entry = entries.nextFile;
  1014.         if (!entry)
  1015.           break;
  1016.         entry instanceof nsILocalFile;
  1017.         if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
  1018.           var linkedDirectory = this._readDirectoryFromFile(entry);
  1019.           if (linkedDirectory && linkedDirectory.exists() && 
  1020.               linkedDirectory.isDirectory()) {
  1021.             locations.push(linkedDirectory);
  1022.             this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
  1023.           }
  1024.         }
  1025.         else
  1026.           locations.push(entry);
  1027.       }
  1028.       entries.close();
  1029.     }
  1030.     catch (e) { 
  1031.     }
  1032.     return new FileEnumerator(locations);
  1033.   },
  1034.   
  1035.   /**
  1036.    * Retrieves the GUID for an item at the specified location.
  1037.    * @param   file
  1038.    *          The location where an item might live.
  1039.    * @returns The ID for an item that might live at the location specified.
  1040.    * 
  1041.    * N.B. This function makes no promises about whether or not this path is 
  1042.    *      actually maintained by this Install Location.
  1043.    */
  1044.   getIDForLocation: function(file) {
  1045.     var section = file.leafName;
  1046.     var filePD = file.persistentDescriptor;
  1047.     if (filePD in this._locationToIDMap) 
  1048.       section = this._locationToIDMap[filePD];
  1049.     
  1050.     if (gIDTest.test(section))
  1051.       return RegExp.$1;
  1052.     return undefined;
  1053.   },
  1054.   
  1055.   /**
  1056.    * See nsIExtensionManager.idl
  1057.    */
  1058.   get location() {
  1059.     return this._location.clone();
  1060.   },
  1061.   
  1062.   /**
  1063.    * See nsIExtensionManager.idl
  1064.    */
  1065.   get restricted() {
  1066.     return this._restricted;
  1067.   },
  1068.   
  1069.   /**
  1070.    * See nsIExtensionManager.idl
  1071.    */
  1072.   get canAccess() {
  1073.     if (this._canAccess != null)
  1074.       return this._canAccess;
  1075.  
  1076.     var testFile = this.location;
  1077.     testFile.append("Access Privileges Test");
  1078.     try {
  1079.       testFile.createUnique(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1080.       testFile.remove(false);
  1081.       this._canAccess = true;
  1082.     }
  1083.     catch (e) {
  1084.       this._canAccess = false;
  1085.     }
  1086.     return this._canAccess;
  1087.   },
  1088.   
  1089.   /**
  1090.    * See nsIExtensionManager.idl
  1091.    */
  1092.   get priority() {
  1093.     return this._priority;
  1094.   },
  1095.   
  1096.   /**
  1097.    * See nsIExtensionManager.idl
  1098.    */
  1099.   getItemLocation: function(id) {
  1100.     var itemLocation = this.location;
  1101.     itemLocation.append(id);
  1102.     if (itemLocation.exists() && !itemLocation.isDirectory())
  1103.       return this._readDirectoryFromFile(itemLocation);
  1104.     if (!itemLocation.exists() && this.canAccess)
  1105.       itemLocation.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1106.     return itemLocation;
  1107.   },
  1108.   
  1109.   /**
  1110.    * See nsIExtensionManager.idl
  1111.    */
  1112.   itemIsManagedIndependently: function(id) {
  1113.     var itemLocation = this.location;
  1114.     itemLocation.append(id);
  1115.     return itemLocation.exists() && !itemLocation.isDirectory();      
  1116.   },
  1117.   
  1118.   /**
  1119.    * See nsIExtensionManager.idl
  1120.    */
  1121.   getItemFile: function(id, filePath) {
  1122.     var itemLocation = this.getItemLocation(id).clone();
  1123.     var parts = filePath.split("/");
  1124.     for (var i = 0; i < parts.length; ++i)
  1125.       itemLocation.append(parts[i]);
  1126.     return itemLocation;
  1127.   },
  1128.  
  1129.   /**
  1130.    * Stages the specified file for later.
  1131.    * @param   file
  1132.    *          The file to stage
  1133.    * @param   id
  1134.    *          The GUID of the item the file represents
  1135.    */
  1136.   stageFile: function(file, id) {
  1137.     var stagedFile = this.location;
  1138.     stagedFile.append(DIR_STAGE);
  1139.     stagedFile.append(id);
  1140.     stagedFile.append(file.leafName);
  1141.  
  1142.     // When an incompatible update is successful the file is already staged
  1143.     if (stagedFile.equals(file))
  1144.       return stagedFile;
  1145.  
  1146.     if (stagedFile.exists()) 
  1147.       stagedFile.remove(false);
  1148.       
  1149.     file.copyTo(stagedFile.parent, stagedFile.leafName);
  1150.     
  1151.     // If the file has incorrect permissions set, correct them now.
  1152.     if (!stagedFile.isWritable())
  1153.       stagedFile.permissions = PERMS_FILE;
  1154.     
  1155.     return stagedFile;
  1156.   },
  1157.   
  1158.   /**
  1159.    * Returns the most recently staged package (e.g. the last XPI or JAR in a
  1160.    * directory) for an item and removes items that do not qualify.
  1161.    * @param   id
  1162.    *          The ID of the staged package
  1163.    * @returns an nsIFile if the package exists otherwise null.
  1164.    */
  1165.   getStageFile: function(id) {
  1166.     var stageFile = null;
  1167.     var stageDir = this.location;
  1168.     stageDir.append(DIR_STAGE);
  1169.     stageDir.append(id);
  1170.     if (!stageDir.exists() || !stageDir.isDirectory())
  1171.       return null;
  1172.     try {
  1173.       var entries = stageDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1174.       while (entries.hasMoreElements()) {
  1175.         var file = entries.nextFile;
  1176.         if (!(file instanceof nsILocalFile))
  1177.           continue;
  1178.         if (file.isDirectory())
  1179.           removeDirRecursive(file);
  1180.         else if (fileIsItemPackage(file)) {
  1181.           if (stageFile)
  1182.             stageFile.remove(false);
  1183.           stageFile = file;
  1184.         }
  1185.         else
  1186.           file.remove(false);
  1187.       }
  1188.     }
  1189.     catch (e) {
  1190.     }
  1191.     if (entries instanceof nsIDirectoryEnumerator)
  1192.       entries.close();
  1193.     return stageFile;
  1194.   },
  1195.   
  1196.   /**
  1197.    * Removes a file from the stage. This cleans up the stage if there is nothing
  1198.    * else left after the remove operation.
  1199.    * @param   file
  1200.    *          The file to remove.
  1201.    */
  1202.   removeFile: function(file) {
  1203.     if (file.exists())
  1204.       file.remove(false);
  1205.     var parent = file.parent;
  1206.     var entries = parent.directoryEntries;    
  1207.     try {
  1208.       // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  1209.       // it has been removed will cause a crash on Mac OS X - bug 292823
  1210.       while (parent && !parent.equals(this.location) &&
  1211.             !entries.hasMoreElements()) {
  1212.         parent.remove(false);
  1213.         parent = parent.parent;
  1214.         entries = parent.directoryEntries;
  1215.       }
  1216.       if (entries instanceof nsIDirectoryEnumerator)
  1217.         entries.close();
  1218.     }
  1219.     catch (e) {
  1220.       LOG("DirectoryInstallLocation::removeFile: failed to remove staged " + 
  1221.           " directory = " + parent.path + ", exception = " + e + "\n");
  1222.     }
  1223.   },
  1224.   
  1225.   /**
  1226.    * See nsISupports.idl
  1227.    */
  1228.   QueryInterface: function (iid) {
  1229.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1230.         !iid.equals(Components.interfaces.nsISupports))
  1231.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1232.     return this;
  1233.   }
  1234. };
  1235.  
  1236. //@line 1237 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1237.  
  1238. const nsIWindowsRegKey = Components.interfaces.nsIWindowsRegKey;
  1239.  
  1240. /**
  1241.  * An object that identifies the location of installed items based on entries
  1242.  * in the Windows registry.  For each application a subkey is defined that
  1243.  * contains a set of values, where the name of each value is a GUID and the
  1244.  * contents of the value is a filesystem path identifying a directory
  1245.  * containing an installed item.
  1246.  *
  1247.  * @param   name
  1248.  *          The string identifier of this Install Location.
  1249.  * @param   rootKey
  1250.  *          The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
  1251.  * @param   restricted
  1252.  *          Indicates that the location may be restricted (e.g., this is
  1253.  *          usually true of a system level install location).
  1254.  * @param   priority
  1255.  *          The priority of this install location.
  1256.  * @constructor
  1257.  */
  1258. function WinRegInstallLocation(name, rootKey, restricted, priority) {
  1259.   this._name = name;
  1260.   this._rootKey = rootKey;
  1261.   this._restricted = restricted;
  1262.   this._priority = priority;
  1263.   this._IDToDirMap = {};
  1264.   this._DirToIDMap = {};
  1265.  
  1266.   // Reading the registry may throw an exception, and that's ok.  In error
  1267.   // cases, we just leave ourselves in the empty state.
  1268.   try {
  1269.     var path = this._appKeyPath + "\\Extensions";
  1270.     var key = Components.classes["@mozilla.org/windows-registry-key;1"]
  1271.                         .createInstance(nsIWindowsRegKey);
  1272.     key.open(this._rootKey, path, nsIWindowsRegKey.ACCESS_READ);
  1273.     this._readAddons(key);
  1274.   } catch (e) {
  1275.     if (key)
  1276.       key.close();
  1277.   }
  1278. }
  1279. WinRegInstallLocation.prototype = {
  1280.   _name       : "",
  1281.   _rootKey    : null,
  1282.   _restricted : false,
  1283.   _priority   : 0,
  1284.   _IDToDirMap : null,  // mapping from ID to directory object
  1285.   _DirToIDMap : null,  // mapping from directory path to ID
  1286.   
  1287.   /**
  1288.    * Retrieves the path of this Application's data key in the registry.
  1289.    */
  1290.   get _appKeyPath() {
  1291.     var appVendor = gApp.vendor;
  1292.     var appName = gApp.name;
  1293.  
  1294. //@line 1299 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1295.   
  1296.     // XULRunner-based apps may intentionally not specify a vendor:
  1297.     if (appVendor != "")
  1298.       appVendor += "\\";
  1299.  
  1300.     return "SOFTWARE\\" + appVendor + appName;
  1301.   },
  1302.  
  1303.   /**
  1304.    * Read the registry and build a mapping between GUID and directory for each
  1305.    * installed item.
  1306.    * @param   key
  1307.    *          The key that contains the GUID->path pairs
  1308.    */
  1309.   _readAddons: function(key) {
  1310.     var count = key.valueCount; 
  1311.     for (var i = 0; i < count; ++i) {
  1312.       var id = key.getValueName(i);
  1313.  
  1314.       var dir = Components.classes["@mozilla.org/file/local;1"]
  1315.                           .createInstance(nsILocalFile);
  1316.       dir.initWithPath(key.readStringValue(id));
  1317.  
  1318.       if (dir.exists() && dir.isDirectory()) {
  1319.         this._IDToDirMap[id] = dir;
  1320.         this._DirToIDMap[dir.path] = id;
  1321.       }
  1322.     }
  1323.   },
  1324.  
  1325.   get name() {
  1326.     return this._name;
  1327.   },
  1328.  
  1329.   get itemLocations() {
  1330.     var locations = [];
  1331.     for (var id in this._IDToDirMap) {
  1332.       locations.push(this._IDToDirMap[id]);
  1333.     }
  1334.     return new FileEnumerator(locations);
  1335.   },
  1336.  
  1337.   get location() {
  1338.     return null;
  1339.   },
  1340.  
  1341.   get restricted() {
  1342.     return this._restricted;
  1343.   },
  1344.  
  1345.   // you should never be able to write to this location
  1346.   get canAccess() {
  1347.     return false;
  1348.   },
  1349.  
  1350.   get priority() {
  1351.     return this._priority;
  1352.   },
  1353.  
  1354.   getItemLocation: function(id) {
  1355.     return this._IDToDirMap[id];
  1356.   },
  1357.  
  1358.   getIDForLocation: function(dir) {
  1359.     return this._DirToIDMap[dir.path];
  1360.   },
  1361.  
  1362.   getItemFile: function(id, filePath) {
  1363.     var itemLocation = this.getItemLocation(id).clone();
  1364.     var parts = filePath.split("/");
  1365.     for (var i = 0; i < parts.length; ++i)
  1366.       itemLocation.append(parts[i]);
  1367.     return itemLocation;
  1368.   },
  1369.  
  1370.   itemIsManagedIndependently: function(id) {
  1371.     return true;
  1372.   },
  1373.  
  1374.   QueryInterface: function(iid) {
  1375.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1376.         !iid.equals(Components.interfaces.nsISupports))
  1377.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1378.     return this;
  1379.   }
  1380. };
  1381.  
  1382. //@line 1387 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1383.  
  1384. /**
  1385.  * An object which handles the installation of an Extension.
  1386.  * @constructor
  1387.  */
  1388. function Installer(ds, id, installLocation, type) {
  1389.   this._ds = ds;
  1390.   this._id = id;
  1391.   this._type = type;
  1392.   this._installLocation = installLocation;
  1393.   this._metadataFile = this._installLocation
  1394.                            .getItemFile(this._id, FILE_INSTALL_MANIFEST);
  1395. }
  1396. Installer.prototype = {
  1397.   // Item metadata
  1398.   _id: null,
  1399.   _ds: null,
  1400.   _installLocation: null,
  1401.   _metadataDS: null,
  1402.   
  1403.   /**
  1404.    * Gets the Install Manifest datasource we are installing from.
  1405.    */
  1406.   get metadataDS() {
  1407.     if (!this._metadataDS) {
  1408.       if (!this._metadataFile.exists()) 
  1409.         return null;
  1410.       this._metadataDS = getInstallManifest(this._metadataFile);
  1411.       if (!this._metadataDS) {
  1412.         LOG("Installer::install: metadata datasource for extension " + 
  1413.             this._id + " at " + this._metadataFile.path + " could not be loaded. " + 
  1414.             " Installation will not proceed.");
  1415.       }
  1416.     }
  1417.     return this._metadataDS;
  1418.   },
  1419.   
  1420.   /**
  1421.    * Installs the Extension
  1422.    * @param   file
  1423.    *          A XPI/JAR file to install from. If this is null or does not exist,
  1424.    *          the item is assumed to be an expanded directory, located at the GUID
  1425.    *          key in the supplied Install Location.
  1426.    */
  1427.   installFromFile: function(file) {
  1428.     // Move files from the staging dir into the extension's final home.
  1429.     if (file && file.exists()) {
  1430.       this._installExtensionFiles(file);
  1431.     }
  1432.  
  1433.     if (!this.metadataDS)
  1434.       return;
  1435.  
  1436.     // Upgrade old-style contents.rdf Chrome Manifests if necessary.
  1437.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1438.       this.upgradeThemeChrome();
  1439.     else
  1440.       this.upgradeExtensionChrome();
  1441.  
  1442.     // Add metadata for the extension to the global extension metadata set
  1443.     this._ds.addItemMetadata(this._id, this.metadataDS, this._installLocation);
  1444.   },
  1445.   
  1446.   /**
  1447.    * Safely extract the Extension's files into the target folder.
  1448.    * @param   file
  1449.    *          The XPI/JAR file to install from.
  1450.    */
  1451.   _installExtensionFiles: function(file) {
  1452.     var installer = this;
  1453.     /**
  1454.       * Callback for |safeInstallOperation| that performs file level installation
  1455.       * steps for an Extension.
  1456.       * @param   extensionID
  1457.       *          The GUID of the Extension being installed.
  1458.       * @param   installLocation 
  1459.       *          The Install Location where the Extension is being installed.
  1460.       * @param   xpiFile
  1461.       *          The source XPI file that contains the Extension.
  1462.       */
  1463.     function extractExtensionFiles(extensionID, installLocation, xpiFile) {
  1464.       // Create a logger to log install operations for uninstall. This must be 
  1465.       // created in the |safeInstallOperation| callback, since it creates a file
  1466.       // in the target directory. If we do this outside of the callback, we may
  1467.       // be clobbering a file we should not be.
  1468.       var zipReader = getZipReaderForFile(xpiFile);
  1469.       
  1470.       // create directories first
  1471.       var entries = zipReader.findEntries("*/");
  1472.       while (entries.hasMoreElements()) {
  1473.         var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1474.         var target = installLocation.getItemFile(extensionID, entry.name);
  1475.         if (!target.exists()) {
  1476.           try {
  1477.             target.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1478.           }
  1479.           catch (e) {
  1480.             LOG("extractExtensionsFiles: failed to create target directory for extraction " + 
  1481.                 " file = " + target.path + ", exception = " + e + "\n");
  1482.           }
  1483.         }
  1484.       }
  1485.  
  1486.       entries = zipReader.findEntries("*");
  1487.       while (entries.hasMoreElements()) {
  1488.         entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1489.         target = installLocation.getItemFile(extensionID, entry.name);
  1490.         if (target.exists())
  1491.           continue;
  1492.  
  1493.         try {
  1494.           target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1495.         }
  1496.         catch (e) {
  1497.           LOG("extractExtensionsFiles: failed to create target file for extraction " + 
  1498.               " file = " + target.path + ", exception = " + e + "\n");
  1499.         }
  1500.         zipReader.extract(entry.name, target);
  1501.       }
  1502.       zipReader.close();
  1503.     }
  1504.  
  1505.     var installer = this;
  1506.     /**
  1507.       * Callback for |safeInstallOperation| that performs file level installation
  1508.       * steps for a Theme.
  1509.       * @param   id
  1510.       *          The GUID of the Theme being installed.
  1511.       * @param   installLocation 
  1512.       *          The Install Location where the Theme is being installed.
  1513.       * @param   jarFile
  1514.       *          The source JAR file that contains the Theme.
  1515.       */
  1516.     function extractThemeFiles(id, installLocation, jarFile) {
  1517.       var themeDirectory = installLocation.getItemLocation(id);
  1518.       var zipReader = getZipReaderForFile(jarFile);
  1519.  
  1520.       // The only critical file is the install.rdf and we would not have
  1521.       // gotten this far without one.
  1522.       var rootFiles = [FILE_INSTALL_MANIFEST, FILE_CHROME_MANIFEST,
  1523.                        "preview.png", "icon.png"];
  1524.       for (var i = 0; i < rootFiles.length; ++i) {
  1525.         try {
  1526.           var entry = zipReader.getEntry(rootFiles[i]);
  1527.           var target = installLocation.getItemFile(id, rootFiles[i]);
  1528.           zipReader.extract(rootFiles[i], target);
  1529.         }
  1530.         catch (e) {
  1531.         }
  1532.       }
  1533.  
  1534.       var manifestFile = installLocation.getItemFile(id, FILE_CHROME_MANIFEST);
  1535.       // new theme structure requires a chrome.manifest file
  1536.       if (manifestFile.exists()) {
  1537.         var entries = zipReader.findEntries(DIR_CHROME + "/*");
  1538.         while (entries.hasMoreElements()) {
  1539.           entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1540.           if (entry.name.substr(entry.name.length - 1, 1) == "/")
  1541.             continue;
  1542.           target = installLocation.getItemFile(id, entry.name);
  1543.           try {
  1544.             target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1545.           }
  1546.           catch (e) {
  1547.             LOG("extractThemeFiles: failed to create target file for extraction " + 
  1548.                 " file = " + target.path + ", exception = " + e + "\n");
  1549.           }
  1550.           zipReader.extract(entry.name, target);
  1551.         }
  1552.         zipReader.close();
  1553.       }
  1554.       else { // old theme structure requires only an install.rdf
  1555.         try {
  1556.           var entry = zipReader.getEntry(FILE_CONTENTS_MANIFEST);
  1557.           var contentsManifestFile = installLocation.getItemFile(id, FILE_CONTENTS_MANIFEST);
  1558.           contentsManifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1559.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  1560.         }
  1561.         catch (e) {
  1562.           zipReader.close();
  1563.           LOG("extractThemeFiles: failed to extract contents.rdf: " + target.path);
  1564.           throw e; // let the safe-op clean up
  1565.         }
  1566.         zipReader.close();
  1567.         var chromeDir = installLocation.getItemFile(id, DIR_CHROME);
  1568.         try {
  1569.           jarFile.copyTo(chromeDir, jarFile.fileName);
  1570.         }
  1571.         catch (e) {
  1572.           LOG("extractThemeFiles: failed to copy theme JAR file to: " + chromeDir.path);
  1573.           throw e; // let the safe-op clean up
  1574.         }
  1575.  
  1576.         if (!installer.metadataDS && installer._type == nsIUpdateItem.TYPE_THEME) {
  1577.           if (contentsManifestFile && contentsManifestFile.exists()) {
  1578.             var contentsManifest = gRDF.GetDataSourceBlocking(getURLSpecFromFile(contentsManifestFile));
  1579.             showOldThemeError(contentsManifest);
  1580.           }
  1581.           LOG("Theme JAR file: " + jarFile.leafName + " contains an Old-Style " + 
  1582.               "Theme that is not compatible with this version of the software.");
  1583.           throw new Error("Old Theme"); // let the safe-op clean up
  1584.         }
  1585.       }
  1586.     }
  1587.  
  1588.     var callback = extractExtensionFiles;
  1589.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1590.       callback = extractThemeFiles;
  1591.     safeInstallOperation(this._id, this._installLocation,
  1592.                           { callback: callback, data: file });
  1593.   },
  1594.   
  1595.   /** 
  1596.    * Upgrade contents.rdf Chrome Manifests used by this Theme to the new 
  1597.    * chrome.manifest format if necessary.
  1598.    */
  1599.   upgradeThemeChrome: function() {
  1600.     // Use the Chrome Registry API to install the theme there
  1601.     var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1602.                        .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1603.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1604.     if (manifestFile.exists() ||
  1605.         this._id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  1606.       return;
  1607.  
  1608.     try {
  1609.       // creates a chrome manifest for themes
  1610.       var manifestURI = getURIFromFile(manifestFile);
  1611.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1612.       // We're relying on the fact that there is only one JAR file
  1613.       // in the "chrome" directory. This is a hack, but it works.
  1614.       var entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1615.       var jarFile = entries.nextFile;
  1616.       if (jarFile) {
  1617.         var jarFileURI = getURIFromFile(jarFile);
  1618.         var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  1619.         var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1620.         var contentsFileURI = getURIFromFile(contentsFile.parent);
  1621.  
  1622.         cr.processContentsManifest(contentsFileURI, manifestURI, contentsURI, false, true);
  1623.       }
  1624.       entries.close();
  1625.       contentsFile.remove(false);
  1626.     }
  1627.     catch (e) {
  1628.       // Failed to register chrome, for any number of reasons - non-existent 
  1629.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1630.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1631.       // extension is uninstalled properly during the subsequent uninstall 
  1632.       // pass in |ExtensionManager::_finalizeOperations|
  1633.       LOG("upgradeThemeChrome: failed for theme " + this._id + " - why " + 
  1634.           "not convert to the new chrome.manifest format while you're at it? " + 
  1635.           "Failure exception: " + e);
  1636.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1637.                   [BundleManager.appName]);
  1638.  
  1639.       var stageFile = this._installLocation.getStageFile(this._id);
  1640.       if (stageFile)
  1641.         this._installLocation.removeFile(stageFile);
  1642.  
  1643.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1644.       StartupCache.write();
  1645.     }
  1646.   },
  1647.  
  1648.   /** 
  1649.    * Upgrade contents.rdf Chrome Manifests used by this Extension to the new 
  1650.    * chrome.manifest format if necessary.
  1651.    */
  1652.   upgradeExtensionChrome: function() {
  1653.     // If the extension is aware of the new flat chrome manifests and has 
  1654.     // included one, just use it instead of generating one from the
  1655.     // install.rdf/contents.rdf data.
  1656.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1657.     if (manifestFile.exists())
  1658.       return;
  1659.  
  1660.     try {
  1661.       // Enumerate the metadata datasource files collection and register chrome
  1662.       // for each file, calling _registerChrome for each.
  1663.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1664.       
  1665.       if (!manifestFile.parent.exists())
  1666.         return;
  1667.  
  1668.       // Even if an extension doesn't have any chrome, we generate an empty
  1669.       // manifest file so that we don't try to upgrade from the "old-style"
  1670.       // chrome manifests at every startup.
  1671.       manifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1672.  
  1673.       var manifestURI = getURIFromFile(manifestFile);
  1674.       var files = this.metadataDS.GetTargets(gInstallManifestRoot, EM_R("file"), true);
  1675.       while (files.hasMoreElements()) {
  1676.         var file = files.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  1677.         var chromeFile = chromeDir.clone();
  1678.         var fileName = file.Value.substr("urn:mozilla:extension:file:".length, file.Value.length);
  1679.         chromeFile.append(fileName);
  1680.  
  1681.         var fileURLSpec = getURLSpecFromFile(chromeFile);
  1682.         if (!chromeFile.isDirectory()) {
  1683.           var zipReader = getZipReaderForFile(chromeFile);
  1684.           fileURLSpec = "jar:" + fileURLSpec + "!/";
  1685.           var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1686.           contentsFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1687.         }
  1688.  
  1689.         var providers = [EM_R("package"), EM_R("skin"), EM_R("locale")];
  1690.         for (var i = 0; i < providers.length; ++i) {
  1691.           var items = this.metadataDS.GetTargets(file, providers[i], true);
  1692.           while (items.hasMoreElements()) {
  1693.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFLiteral);
  1694.             var fileURI = newURI(fileURLSpec + item.Value);
  1695.             // Extract the contents.rdf files instead of opening them inside of
  1696.             // the jar. This prevents the jar from being cached by the zip
  1697.             // reader which will keep the jar in use and prevent deletion.
  1698.             if (zipReader) {
  1699.               zipReader.extract(item.Value + FILE_CONTENTS_MANIFEST, contentsFile);
  1700.               var contentsFileURI = getURIFromFile(contentsFile.parent);
  1701.             }
  1702.             else
  1703.               contentsFileURI = fileURI;
  1704.  
  1705.             var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1706.                                .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1707.             cr.processContentsManifest(contentsFileURI, manifestURI, fileURI, true, false);
  1708.           }
  1709.         }
  1710.         if (zipReader) {
  1711.           zipReader.close();
  1712.           zipReader = null;
  1713.           contentsFile.remove(false);
  1714.         }
  1715.       }
  1716.     }
  1717.     catch (e) {
  1718.       // Failed to register chrome, for any number of reasons - non-existent 
  1719.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1720.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1721.       // extension is uninstalled properly during the subsequent uninstall 
  1722.       // pass in |ExtensionManager::_finalizeOperations|
  1723.       LOG("upgradeExtensionChrome: failed for extension " + this._id + " - why " + 
  1724.           "not convert to the new chrome.manifest format while you're at it? " + 
  1725.           "Failure exception: " + e);
  1726.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1727.                   [BundleManager.appName]);
  1728.  
  1729.       var stageFile = this._installLocation.getStageFile(this._id);
  1730.       if (stageFile)
  1731.         this._installLocation.removeFile(stageFile);
  1732.  
  1733.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1734.       StartupCache.write();
  1735.     }
  1736.   }  
  1737. };
  1738.  
  1739. /**
  1740.  * Safely attempt to perform a caller-defined install operation for a given
  1741.  * item ID. Using aggressive success-safety checks, this function will attempt
  1742.  * to move an existing location for an item aside and then allow installation
  1743.  * into the appropriate folder. If any operation fails the installation will 
  1744.  * abort and roll back from the moved-aside old version.
  1745.  * @param   itemID
  1746.  *          The GUID of the item to perform the operation on.
  1747.  * @param   installLocation
  1748.  *          The Install Location where the item is installed.
  1749.  * @param   installCallback
  1750.  *          A caller supplied JS object with the following properties:
  1751.  *          "data"      A data parameter to be passed to the callback.
  1752.  *          "callback"  A function to perform the install operation. This
  1753.  *                      function is passed three parameters:
  1754.  *                      1. The GUID of the item being operated on.
  1755.  *                      2. The Install Location where the item is installed.
  1756.  *                      3. The "data" parameter on the installCallback object.
  1757.  */
  1758. function safeInstallOperation(itemID, installLocation, installCallback) {
  1759.   var movedFiles = [];
  1760.   
  1761.   /**
  1762.    * Reverts a deep move by moving backed up files back to their original
  1763.    * location.
  1764.    */
  1765.   function rollbackMove()
  1766.   {
  1767.     for (var i = 0; i < movedFiles.length; ++i) {
  1768.       var oldFile = movedFiles[i].oldFile;
  1769.       var newFile = movedFiles[i].newFile;
  1770.       try {
  1771.         newFile.moveTo(oldFile.parent, newFile.leafName);
  1772.       }
  1773.       catch (e) {
  1774.         LOG("safeInstallOperation: failed to roll back files after an install " + 
  1775.             "operation failed. Failed to roll back: " + newFile.path + " to: " + 
  1776.             oldFile.path + " ... aborting installation.");
  1777.         throw e;
  1778.       }
  1779.     }
  1780.   }
  1781.   
  1782.   /**
  1783.    * Moves a file to a new folder.
  1784.    * @param   file
  1785.    *          The file to move
  1786.    * @param   destination
  1787.    *          The target folder
  1788.    */
  1789.   function moveFile(file, destination) {
  1790.     try {
  1791.       var oldFile = file.clone();
  1792.       file.moveTo(destination, file.leafName);
  1793.       movedFiles.push({ oldFile: oldFile, newFile: file });
  1794.     }
  1795.     catch (e) {
  1796.       LOG("safeInstallOperation: failed to back up file: " + file.path + " to: " + 
  1797.           destination.path + " ... rolling back file moves and aborting " + 
  1798.           "installation.");
  1799.       rollbackMove();
  1800.       throw e;
  1801.     }
  1802.   }
  1803.   
  1804.   /**
  1805.    * Moves a directory to a new location. If any part of the move fails,
  1806.    * files already moved will be rolled back.
  1807.    * @param   sourceDir
  1808.    *          The directory to move
  1809.    * @param   targetDir
  1810.    *          The destination directory
  1811.    * @param   currentDir
  1812.    *          The current directory (a subdirectory of |sourceDir| or 
  1813.    *          |sourceDir| itself) we are moving files from.
  1814.    */
  1815.   function moveDirectory(sourceDir, targetDir, currentDir) {
  1816.     var entries = currentDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1817.     while (true) {
  1818.       var entry = entries.nextFile;
  1819.       if (!entry)
  1820.         break;
  1821.       if (entry.isDirectory())
  1822.         moveDirectory(sourceDir, targetDir, entry);
  1823.       else if (entry instanceof nsILocalFile) {
  1824.         var rd = entry.getRelativeDescriptor(sourceDir);
  1825.         var destination = targetDir.clone().QueryInterface(nsILocalFile);
  1826.         destination.setRelativeDescriptor(targetDir, rd);
  1827.         moveFile(entry, destination.parent);
  1828.       }
  1829.     }
  1830.     entries.close();
  1831.   }
  1832.   
  1833.   /**
  1834.    * Removes the temporary backup directory where we stored files. 
  1835.    * @param   directory
  1836.    *          The backup directory to remove
  1837.    */
  1838.   function cleanUpTrash(directory) {
  1839.     try {
  1840.       // Us-generated. Safe.
  1841.       if (directory && directory.exists())
  1842.         removeDirRecursive(directory);
  1843.     }
  1844.     catch (e) {
  1845.       LOG("safeInstallOperation: failed to clean up the temporary backup of the " + 
  1846.           "older version: " + itemLocationTrash.path);
  1847.       // This is a non-fatal error. Annoying, but non-fatal. 
  1848.     }
  1849.   }
  1850.   
  1851.   var itemLocation = installLocation.getItemLocation(itemID);
  1852.   if (itemLocation.exists()) {
  1853.     var trashDirName = itemID + "-trash";
  1854.     var itemLocationTrash = itemLocation.parent.clone();
  1855.     itemLocationTrash.append(trashDirName);
  1856.     if (itemLocationTrash.exists()) {
  1857.       // We can remove recursively here since this is a folder we created, not
  1858.       // one the user specified. If this fails, it'll throw, and the caller 
  1859.       // should stop installation.
  1860.       try {
  1861.         removeDirRecursive(itemLocationTrash);
  1862.       }
  1863.       catch (e) {
  1864.         LOG("safeFileOperation: failed to remove existing trash directory " + 
  1865.             itemLocationTrash.path + " ... aborting installation.");
  1866.         throw e;
  1867.       }
  1868.     }
  1869.     
  1870.     // Move the directory that contains the existing version of the item aside, 
  1871.     // into {GUID}-trash. This will throw if there's a failure and the install
  1872.     // will abort.
  1873.     moveDirectory(itemLocation, itemLocationTrash, itemLocation);
  1874.     
  1875.     // Clean up the original location, if necessary. Again, this is a path we 
  1876.     // generated, so it is safe to recursively delete.
  1877.     try {
  1878.       removeDirRecursive(itemLocation);
  1879.     }
  1880.     catch (e) {
  1881.       LOG("safeInstallOperation: failed to clean up item location after its contents " + 
  1882.           "were properly backed up. Failed to clean up: " + itemLocation.path + 
  1883.           " ... rolling back file moves and aborting installation.");
  1884.       rollbackMove();
  1885.       cleanUpTrash(itemLocationTrash);
  1886.       throw e;
  1887.     }
  1888.   }
  1889.       
  1890.   // Now tell the client to do their stuff.
  1891.   try {
  1892.     installCallback.callback(itemID, installLocation, installCallback.data);
  1893.   }
  1894.   catch (e) {
  1895.     // This means the install operation failed. Remove everything and roll back.
  1896.     LOG("safeInstallOperation: install operation (caller-supplied callback) failed, " + 
  1897.         "rolling back file moves and aborting installation.");
  1898.     try {
  1899.       // Us-generated. Safe.
  1900.       removeDirRecursive(itemLocation);
  1901.     }
  1902.     catch (e) {
  1903.       LOG("safeInstallOperation: failed to remove the folder we failed to install " + 
  1904.           "an item into: " + itemLocation.path + " -- There is not much to suggest " + 
  1905.           "here... maybe restart and try again?");
  1906.       cleanUpTrash(itemLocationTrash);
  1907.       throw e;
  1908.     }
  1909.     rollbackMove();
  1910.     cleanUpTrash(itemLocationTrash);
  1911.     throw e;        
  1912.   }
  1913.   
  1914.   // Now, and only now - after everything else has succeeded (against all odds!) 
  1915.   // remove the {GUID}-trash directory where we stashed the old version of the 
  1916.   // item.
  1917.   cleanUpTrash(itemLocationTrash);
  1918. }
  1919.  
  1920. /**
  1921.  * Manages the list of pending operations.
  1922.  */
  1923. var PendingOperations = {
  1924.   _ops: { },
  1925.  
  1926.   /**
  1927.    * Adds an entry to the Pending Operations List
  1928.    * @param   opType
  1929.    *          The type of Operation to be performed
  1930.    * @param   entry
  1931.    *          A JS Object representing the item to be operated on:
  1932.    *          "locationKey"   The name of the Install Location where the item
  1933.    *                          is installed.
  1934.    *          "id"            The GUID of the item.
  1935.    */
  1936.   addItem: function(opType, entry) {
  1937.     if (!(opType in this._ops))
  1938.       this._ops[opType] = [];
  1939.     this._ops[opType].push(entry);
  1940.   },
  1941.   
  1942.   /**
  1943.    * Removes a Pending Operation from the list
  1944.    * @param   opType
  1945.    *          The type of Operation being removed
  1946.    * @param   id
  1947.    *          The GUID of the item to remove the entry for
  1948.    */
  1949.   clearItem: function(opType, id) {
  1950.     if (!(opType in this._ops))
  1951.       return;
  1952.     for (var i = 0; i < this._ops[opType].length; ++i) {
  1953.       if (this._ops[opType][i].id == id)
  1954.         this._ops[opType].splice(i, 1);
  1955.     }
  1956.   },
  1957.   
  1958.   /**
  1959.    * Remove all Pending Operations of a certain type
  1960.    * @param   opType
  1961.    *          The type of Operation to remove all entries for
  1962.    */
  1963.   clearItems: function(opType) {
  1964.     if (opType in this._ops)
  1965.       this._ops[opType] = [];
  1966.   },
  1967.   
  1968.   /**
  1969.    * Get an array of operations of a certain type
  1970.    * @param   opType
  1971.    *          The type of Operation to return a list of
  1972.    */
  1973.   getOperations: function(opType) {
  1974.     return opType in this._ops ? this._ops[opType] : [];
  1975.   },
  1976.   
  1977.   /**
  1978.    * The total number of Pending Operations, for all types.
  1979.    */
  1980.   get size() {
  1981.     var size = 0;
  1982.     for (var opType in this._ops)
  1983.       size += this._ops[opType].length;
  1984.     return size;
  1985.   }
  1986. };
  1987.  
  1988. /**
  1989.  * Manages registered Install Locations
  1990.  */
  1991. var InstallLocations = { 
  1992.   _locations: { },
  1993.  
  1994.   /**
  1995.    * A nsISimpleEnumerator of all available Install Locations.
  1996.    */
  1997.   get enumeration() {
  1998.     var installLocations = [];
  1999.     for (var key in this._locations) 
  2000.       installLocations.push(InstallLocations.get(key));
  2001.     return new ArrayEnumerator(installLocations);
  2002.   },
  2003.   
  2004.   /**
  2005.    * Gets a named Install Location
  2006.    * @param   name
  2007.    *          The name of the Install Location to get
  2008.    */
  2009.   get: function(name) {
  2010.     return name in this._locations ? this._locations[name] : null;
  2011.   },
  2012.   
  2013.   /**
  2014.    * Registers an Install Location
  2015.    * @param   installLocation
  2016.    *          The Install Location to register
  2017.    */
  2018.   put: function(installLocation) {
  2019.     this._locations[installLocation.name] = installLocation;
  2020.   }
  2021. };
  2022.  
  2023. /**
  2024.  * Manages the Startup Cache. The Startup Cache is a representation
  2025.  * of the contents of extensions.cache, a list of all
  2026.  * items the Extension System knows about, whether or not they
  2027.  * are active or visible.
  2028.  */
  2029. var StartupCache = {
  2030.   /**
  2031.    * Location Name -> GUID hash of entries from the Startup Cache file
  2032.    * Each entry has the following properties:
  2033.    *  "descriptor"    The location on disk of the item
  2034.    *  "mtime"         The time the location was last modified
  2035.    *  "op"            Any pending operations on this item.
  2036.    *  "location"      The Install Location name where the item is installed.
  2037.    */
  2038.   entries: { },
  2039.  
  2040.   /**
  2041.    * Puts an entry into the Startup Cache
  2042.    * @param   installLocation
  2043.    *          The Install Location where the item is installed
  2044.    * @param   id
  2045.    *          The GUID of the item
  2046.    * @param   op
  2047.    *          The name of the operation to be performed
  2048.    * @param   shouldCreate
  2049.    *          Whether or not we should create a new entry for this item
  2050.    *          in the cache if one does not already exist. 
  2051.    */
  2052.   put: function(installLocation, id, op, shouldCreate) {
  2053.     var itemLocation = installLocation.getItemLocation(id);
  2054.  
  2055.     var descriptor = null;
  2056.     var mtime = null;
  2057.     if (itemLocation) {
  2058.       itemLocation.QueryInterface(nsILocalFile);
  2059.       descriptor = getDescriptorFromFile(itemLocation, installLocation);
  2060.       if (itemLocation.exists() && itemLocation.isDirectory())
  2061.         mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2062.     }
  2063.  
  2064.     this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
  2065.   },
  2066.  
  2067.   /**
  2068.    * Private helper function for putting an entry into the Startup Cache
  2069.    * without relying on the presence of its associated nsIInstallLocation
  2070.    * instance.
  2071.    *
  2072.    * @param key
  2073.    *        The install location name.
  2074.    * @param id
  2075.    *        The ID of the item.
  2076.    * @param descriptor
  2077.    *        Value returned from absoluteDescriptor.  May be null, in which
  2078.    *        case the descriptor field is not updated.
  2079.    * @param mtime
  2080.    *        The last modified time of the item.  May be null, in which case the
  2081.    *        descriptor field is not updated.
  2082.    * @param op
  2083.    *        The OP code to store with the entry.
  2084.    * @param shouldCreate
  2085.    *        Boolean value indicating whether to create or delete the entry.
  2086.    */
  2087.   _putRaw: function(key, id, descriptor, mtime, op, shouldCreate) {
  2088.     if (!(key in this.entries))
  2089.       this.entries[key] = { };
  2090.     if (!(id in this.entries[key]))
  2091.       this.entries[key][id] = { };
  2092.     if (shouldCreate) {
  2093.       if (!this.entries[key][id]) 
  2094.         this.entries[key][id] = { };
  2095.  
  2096.       var entry = this.entries[key][id];
  2097.  
  2098.       if (descriptor)
  2099.         entry.descriptor = descriptor;
  2100.       if (mtime) 
  2101.         entry.mtime = mtime;
  2102.       entry.op = op;
  2103.       entry.location = key;
  2104.     }
  2105.     else
  2106.       this.entries[key][id] = null;
  2107.   },
  2108.   
  2109.   /**
  2110.    * Clears an entry from the Startup Cache
  2111.    * @param   installLocation
  2112.    *          The Install Location where item is installed
  2113.    * @param   id
  2114.    *          The GUID of the item.
  2115.    */
  2116.   clearEntry: function(installLocation, id) {
  2117.     var key = installLocation.name;
  2118.     if (key in this.entries && id in this.entries[key])
  2119.       this.entries[key][id] = null;
  2120.   },
  2121.   
  2122.   /**
  2123.    * Get all the startup cache entries for a particular ID.
  2124.    * @param   id
  2125.    *          The GUID of the item to locate.
  2126.    * @returns An array of Startup Cache entries for the specified ID.
  2127.    */
  2128.   findEntries: function(id) {
  2129.     var entries = [];
  2130.     for (var key in this.entries) {
  2131.       if (id in this.entries[key]) 
  2132.         entries.push(this.entries[key][id]);
  2133.     }
  2134.     return entries;
  2135.   },
  2136.  
  2137.   /**
  2138.    * Call a function on each entry.  The callback function takes a single
  2139.    * parameter, which is an entry object.
  2140.    */
  2141.   forEachEntry: function(callback) {
  2142.     for (var key in this.entries) {
  2143.       for (id in this.entries[key])
  2144.         callback(this.entries[key][id]);
  2145.     }
  2146.   },
  2147.   
  2148.   /** 
  2149.    * Read the Item-Change manifest file into a hash of properties.
  2150.    * The Item-Change manifest currently holds a list of paths, with the last
  2151.    * mtime for each path, and the GUID of the item at that path.
  2152.    */
  2153.   read: function() {
  2154.     var itemChangeManifest = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2155.     if (!itemChangeManifest.exists()) {
  2156.       // There is no change manifest for some reason, either we're in an initial
  2157.       // state or something went wrong with one of the other files and the
  2158.       // change manifest was removed. Return an empty dataset and rebuild.
  2159.       return;
  2160.     }
  2161.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  2162.                         .createInstance(Components.interfaces.nsIFileInputStream);
  2163.     fis.init(itemChangeManifest, -1, -1, false);
  2164.     if (fis instanceof nsILineInputStream) {
  2165.       var line = { value: "" };
  2166.       var more = false;
  2167.       do {
  2168.         more = fis.readLine(line);
  2169.         if (line.value) {
  2170.           // The Item-Change manifest is formatted like so:
  2171.           //  (pd = descriptor)
  2172.           // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2173.           // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
  2174.           // ...
  2175.           // We hash on location-key first, because we don't want to have to 
  2176.           // spin up the main extensions datasource on every start to determine
  2177.           // the Install Location for an item.
  2178.           // We hash on guid second, because we want a way to quickly determine
  2179.           // item GUID during a check loop that runs on every startup.
  2180.           var parts = line.value.split("\t");
  2181.           var op = parts[4];
  2182.           this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
  2183.           if (op)
  2184.             PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
  2185.         }
  2186.       }
  2187.       while (more);
  2188.     }
  2189.     fis.close();
  2190.   },
  2191.  
  2192.   /**
  2193.    * Writes the Startup Cache to disk
  2194.    */
  2195.   write: function() {
  2196.     var extensionsCacheFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2197.     var fos = openSafeFileOutputStream(extensionsCacheFile);
  2198.     for (var locationKey in this.entries) {
  2199.       for (var id in this.entries[locationKey]) {
  2200.         var entry = this.entries[locationKey][id];
  2201.         if (entry) {
  2202.           try {
  2203.             var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
  2204.  
  2205.             // Update our knowledge of this item's last-modified-time.
  2206.             // XXXdarin: this may cause us to miss changes in some cases.
  2207.             var itemMTime = 0;
  2208.             if (itemLocation.exists() && itemLocation.isDirectory())
  2209.               itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2210.  
  2211.             // Each line in the startup cache manifest is in this form:
  2212.             // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2213.             var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
  2214.                        itemMTime + "\t" + entry.op + "\r\n";
  2215.             fos.write(line, line.length);
  2216.           }
  2217.           catch (e) {}
  2218.         }
  2219.       }
  2220.     }
  2221.     closeSafeFileOutputStream(fos);
  2222.   }
  2223. };
  2224.  
  2225. /**
  2226.  * Installs, manages and tracks compatibility for Extensions and Themes
  2227.  * @constructor
  2228.  */
  2229. function ExtensionManager() {
  2230.   gApp = Components.classes["@mozilla.org/xre/app-info;1"]
  2231.                    .getService(Components.interfaces.nsIXULAppInfo)
  2232.                    .QueryInterface(Components.interfaces.nsIXULRuntime);
  2233.   gOSTarget = gApp.OS;
  2234.   try {
  2235.     gXPCOMABI = gApp.XPCOMABI;
  2236.   } catch (ex) {
  2237.     // Provide a default for gXPCOMABI. It won't be compared to an
  2238.     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
  2239.     // as targetPlatform), but it will be displayed in error messages and
  2240.     // transmitted to update URLs.
  2241.     gXPCOMABI = UNKNOWN_XPCOM_ABI;
  2242.   }
  2243.   gPref = Components.classes["@mozilla.org/preferences-service;1"]
  2244.                     .getService(Components.interfaces.nsIPrefBranch2);
  2245.   gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2246.   gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
  2247.  
  2248.   gOS = Components.classes["@mozilla.org/observer-service;1"]
  2249.                   .getService(Components.interfaces.nsIObserverService);
  2250.   gOS.addObserver(this, "xpcom-shutdown", false);
  2251.  
  2252.   gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  2253.                        .getService(Components.interfaces.nsIConsoleService);  
  2254.   
  2255.   gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  2256.                    .getService(Components.interfaces.nsIRDFService);
  2257.   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  2258.   
  2259.   // Register Global Install Location
  2260.   var appGlobalExtensions = getDirNoCreate(KEY_APPDIR, [DIR_EXTENSIONS]);
  2261.   var priority = nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2262.   var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL, 
  2263.                                                     appGlobalExtensions, true,
  2264.                                                     priority);
  2265.   InstallLocations.put(globalLocation);
  2266.  
  2267.   // Register App-Profile Install Location
  2268.   var appProfileExtensions = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS]);
  2269.   var priority = nsIInstallLocation.PRIORITY_APP_PROFILE;
  2270.   var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE, 
  2271.                                                      appProfileExtensions, false,
  2272.                                                      priority);
  2273.   InstallLocations.put(profileLocation);
  2274.  
  2275. //@line 2280 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2276.   // Register HKEY_LOCAL_MACHINE Install Location
  2277.   InstallLocations.put(
  2278.       new WinRegInstallLocation("winreg-app-global",
  2279.                                 nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
  2280.                                 true,
  2281.                                 nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10));
  2282.  
  2283.   // Register HKEY_CURRENT_USER Install Location
  2284.   InstallLocations.put(
  2285.       new WinRegInstallLocation("winreg-app-user",
  2286.                                 nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
  2287.                                 false,
  2288.                                 nsIInstallLocation.PRIORITY_APP_SYSTEM_USER + 10));
  2289. //@line 2294 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2290.  
  2291.   // Register Additional Install Locations
  2292.   var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  2293.                                   .getService(Components.interfaces.nsICategoryManager);
  2294.   var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
  2295.   while (locations.hasMoreElements()) {
  2296.     var entry = locations.getNext().QueryInterface(Components.interfaces.nsISupportsCString).data;
  2297.     var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
  2298.     var location = Components.classes[contractID].getService(nsIInstallLocation);
  2299.     InstallLocations.put(location);
  2300.   }
  2301. }
  2302.  
  2303. ExtensionManager.prototype = {
  2304.   /**
  2305.    * See nsIObserver.idl
  2306.    */
  2307.   observe: function(subject, topic, data) {
  2308.     switch (topic) {
  2309.     case "app-startup":
  2310.       gOS.addObserver(this, "profile-after-change", false);
  2311.       break;
  2312.     case "profile-after-change":
  2313.       this._profileSelected();
  2314.       break;
  2315.     case "quit-application-requested":
  2316.       this._confirmCancelDownloadsOnQuit(subject);
  2317.       break;
  2318.     case "offline-requested":
  2319.       this._confirmCancelDownloadsOnOffline(subject);
  2320.       break;
  2321.     case "xpcom-shutdown":
  2322.       this._shutdown();
  2323.       break;
  2324.     case "nsPref:changed":
  2325.       if (data == PREF_EM_LOGGING_ENABLED)
  2326.         this._loggingToggled();
  2327.       break;
  2328.     }
  2329.   },
  2330.   
  2331.   /**
  2332.    * Refresh the logging enabled global from preferences when the user changes
  2333.    * the preference settting.
  2334.    */
  2335.   _loggingToggled: function() {
  2336.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2337.   },
  2338.  
  2339.   /**
  2340.    * Initialize the system after a profile has been selected.
  2341.    */  
  2342.   _profileSelected: function() {
  2343.     // Tell the Chrome Registry which Skin to select
  2344.     try {
  2345.       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
  2346.         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  2347.         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
  2348.         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
  2349.         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  2350.       }
  2351.     }
  2352.     catch (e) {
  2353.     }
  2354.   },
  2355.   
  2356.   /**
  2357.    * Clean up on application shutdown to avoid leaks.
  2358.    */
  2359.   _shutdown: function() {
  2360.     gOS.removeObserver(this, "xpcom-shutdown");    
  2361.  
  2362.     // Release strongly held services.
  2363.     gOS = null;
  2364.     if (this._ds && gRDF) 
  2365.       gRDF.UnregisterDataSource(this._ds)
  2366.     gRDF = null;
  2367.     if (gPref)
  2368.       gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  2369.     gPref = null;
  2370.     gConsole = null;
  2371.     gVersionChecker = null;
  2372.     gInstallManifestRoot = null;
  2373.     gApp = null;
  2374.   },
  2375.   
  2376.   /**
  2377.    * Check for presence of critical Extension system files. If any is missing, 
  2378.    * delete the others and signal that the system needs to rebuild them all
  2379.    * from scratch.
  2380.    * @returns true if any critical file is missing and the system needs to
  2381.    *          be rebuilt, false otherwise.
  2382.    */
  2383.   _ensureDatasetIntegrity: function () {
  2384.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  2385.     var extensionsINI = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2386.     var extensionsCache = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2387.     
  2388.     var dsExists = extensionsDS.exists();
  2389.     var iniExists = extensionsINI.exists();
  2390.     var cacheExists = extensionsCache.exists();
  2391.  
  2392.     if (dsExists && iniExists && cacheExists)
  2393.       return false;
  2394.  
  2395.     // If any of the files are missing, remove the .ini file
  2396.     if (iniExists)
  2397.       extensionsINI.remove(false);
  2398.  
  2399.     // If the extensions datasource is missing remove the .cache file if it exists
  2400.     if (!dsExists && cacheExists)
  2401.       extensionsCache.remove(false);
  2402.  
  2403.     return true;
  2404.   },
  2405.   
  2406.   /**
  2407.    * See nsIExtensionManager.idl
  2408.    */
  2409.   start: function(commandLine) {
  2410.     var isDirty = false;
  2411.     var forceAutoReg = false;
  2412.     
  2413.     // Somehow the component list went away, and for that reason the new one
  2414.     // generated by this function is going to result in a different compreg.
  2415.     // We must force a restart.
  2416.     var componentList = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2417.     if (!componentList.exists())
  2418.       forceAutoReg = true;
  2419.     
  2420.     // Check for missing manifests - e.g. missing extensions.ini, missing
  2421.     // extensions.cache, extensions.rdf etc. If any of these files 
  2422.     // is missing then we are in some kind of weird or initial state and need
  2423.     // to force a regeneration.
  2424.     if (this._ensureDatasetIntegrity())
  2425.       isDirty = true;
  2426.  
  2427.     // Configure any items that are being installed, uninstalled or upgraded 
  2428.     // by being added, removed or modified by another process. We must do this
  2429.     // on every startup since there is no way we can tell if this has happened
  2430.     // or not!
  2431.     if (this._checkForFileChanges())
  2432.       isDirty = true;
  2433.  
  2434.     if (PendingOperations.size != 0)
  2435.       isDirty = true;
  2436.  
  2437.     // Extension Changes
  2438.     if (isDirty) {
  2439.       var needsRestart = this._finishOperations();
  2440.  
  2441.       if (forceAutoReg) {
  2442.         this._extensionListChanged = true;
  2443.         needsRestart = true;
  2444.       }
  2445.       return needsRestart;
  2446.     }
  2447.       
  2448.     this._startTimers();
  2449.  
  2450.     return false;
  2451.   },
  2452.   
  2453.   /**
  2454.    * Begins all background update check timers
  2455.    */
  2456.   _startTimers: function() {
  2457.     // Register a background update check timer
  2458.     var tm = 
  2459.         Components.classes["@mozilla.org/updates/timer-manager;1"]
  2460.                   .getService(Components.interfaces.nsIUpdateTimerManager);
  2461.     var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400); 
  2462.     tm.registerTimer("addon-background-update-timer", this, interval);
  2463.   },
  2464.   
  2465.   /**
  2466.    * Notified when a timer fires
  2467.    * @param   timer
  2468.    *          The timer that fired
  2469.    */
  2470.   notify: function(timer) {
  2471.     if (getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
  2472.       this.update([], 0, false, null);
  2473.   },
  2474.   
  2475.   /**
  2476.    * See nsIExtensionManager.idl
  2477.    */
  2478.   handleCommandLineArgs: function(commandLine) {
  2479.     try {
  2480.       var globalExtension = commandLine.handleFlagWithParam("install-global-extension", false);
  2481.       if (globalExtension) {
  2482.         var file = commandLine.resolveFile(globalExtension);
  2483.         this._installGlobalItem(file);
  2484.       }
  2485.       var globalTheme = commandLine.handleFlagWithParam("install-global-theme", false);
  2486.       if (globalTheme) {
  2487.         file = commandLine.resolveFile(globalTheme);
  2488.         this._installGlobalItem(file);
  2489.       }
  2490.     }
  2491.     catch (e) { 
  2492.       LOG("ExtensionManager:handleCommandLineArgs - failure, catching exception - lineno: " +
  2493.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  2494.     }
  2495.     commandLine.preventDefault = true;
  2496.   },
  2497.  
  2498.   /**
  2499.    * Installs an XPI/JAR file into the KEY_APP_GLOBAL install location.
  2500.    * @param   file
  2501.    *          The XPI/JAR file to extract
  2502.    */
  2503.   _installGlobalItem: function(file) {
  2504.     if (!file || !file.exists())
  2505.       throw new Error("Unable to find the file specified on the command line!");
  2506.     var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2507.     if (!installManifestFile.exists())
  2508.       throw new Error("The package is missing an install manifest!");
  2509.     var installManifest = getInstallManifest(installManifestFile);
  2510.     installManifestFile.remove(false);
  2511.     var installData = this._getInstallData(installManifest);
  2512.     var installer = new Installer(installManifest, installData.id,
  2513.                                   InstallLocations.get(KEY_APP_GLOBAL),
  2514.                                   installData.type);
  2515.     installer._installExtensionFiles(file);
  2516.     if (installData.type == nsIUpdateItem.TYPE_EXTENSION)
  2517.       installer.upgradeExtensionChrome();
  2518.     else
  2519.       installer.upgradeThemeChrome();
  2520.   },
  2521.  
  2522.   /**
  2523.    * Check to see if a file is a XPI/JAR file that the user dropped into this
  2524.    * Install Location. (i.e. a XPI that is not a staged XPI from an install 
  2525.    * transaction that is currently in operation). 
  2526.    * @param   file
  2527.    *          The XPI/JAR file to configure
  2528.    * @param   location
  2529.    *          The Install Location where this file was found.
  2530.    * @returns A nsIUpdateItem representing the dropped XPI if this file was a 
  2531.    *          XPI/JAR that needs installation, null otherwise.
  2532.    */
  2533.   _getItemForDroppedFile: function(file, location) {
  2534.     if (fileIsItemPackage(file)) {
  2535.       // We know nothing about this item, it is not something we've
  2536.       // staged in preparation for finalization, so assume it's something
  2537.       // the user dropped in.
  2538.       LOG("A Item Package appeared at: " + file.path + " that we know " + 
  2539.           "nothing about, assuming it was dropped in by the user and " + 
  2540.           "configuring for installation now. Location Key: " + location.name);
  2541.  
  2542.       var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2543.       if (!installManifestFile.exists())
  2544.         return null;
  2545.       var installManifest = getInstallManifest(installManifestFile);
  2546.       installManifestFile.remove(false);
  2547.       var ds = this.datasource;
  2548.       var installData = this._getInstallData(installManifest);
  2549.       var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
  2550.       return makeItem(installData.id,
  2551.                       installData.version,
  2552.                       location.name,
  2553.                       targetAppInfo ? targetAppInfo.minVersion : "",
  2554.                       targetAppInfo ? targetAppInfo.maxVersion : "",
  2555.                       getManifestProperty(installManifest, "name"),
  2556.                       "", /* XPI Update URL */
  2557.                       "", /* XPI Update Hash */
  2558.                       getManifestProperty(installManifest, "iconURL"),
  2559.                       getManifestProperty(installManifest, "updateURL"),
  2560.                       installData.type);
  2561.     }
  2562.     return null;
  2563.   },
  2564.   
  2565.   /**
  2566.    * Check for changes to items that were made independently of the Extension 
  2567.    * Manager, e.g. items were added or removed from a Install Location or items
  2568.    * in an Install Location changed. 
  2569.    */
  2570.   _checkForFileChanges: function() {
  2571.     var em = this;
  2572.     /** 
  2573.      * Configure an item that was installed or upgraded by another process
  2574.      * so that |_finishOperations| can properly complete processing and 
  2575.      * registration. 
  2576.      * As this is the only point at which we can reliably know the Install
  2577.      * Location of this item, we use this as an opportunity to:
  2578.      * 1. Check that this item is compatible with this Firefox version.
  2579.      * 2. If it is, configure the item by using the supplied callback.
  2580.      *    We do not do any special handling in the case that the item is
  2581.      *    not compatible with this version other than to simply not register
  2582.      *    it and log that fact - there is no "phone home" check for updates. 
  2583.      *    It may or may not make sense to do this, but for now we'll just
  2584.      *    not register.
  2585.      * @param   id
  2586.      *          The GUID of the item to validate and configure.
  2587.      * @param   location
  2588.      *          The Install Location where this item is installed.
  2589.      * @param   callback
  2590.      *          The callback that configures the item for installation upon
  2591.      *          successful validation.
  2592.      */      
  2593.     function installItem(id, location, callback) {
  2594.       // As this is the only pint at which we reliably know the installation
  2595.       var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  2596.       if (installRDF.exists()) {
  2597.         LOG("Item Installed/Upgraded at Install Location: " + location.name + 
  2598.             " Item ID: " + id + ", attempting to register...");
  2599.         var installManifest = getInstallManifest(installRDF);
  2600.         var installData = em._getInstallData(installManifest);
  2601.         if (installData.error == INSTALLERROR_SUCCESS) {
  2602.           LOG("... success, item is compatible");
  2603.           callback(installManifest, installData.id, location, installData.type);
  2604.         }
  2605.         else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
  2606.           LOG("... success, item installed but is not compatible");
  2607.           callback(installManifest, installData.id, location, installData.type);
  2608.           em._appDisableItem(id);
  2609.         }
  2610.         else {
  2611.           /**
  2612.            * Turns an error code into a message for logging
  2613.            * @param   error
  2614.            *          an Install Error code
  2615.            * @returns A string message to be logged.
  2616.            */
  2617.           function translateErrorMessage(error) {
  2618.             switch (error) {
  2619.             case INSTALLERROR_INVALID_GUID:
  2620.               return "Invalid GUID";
  2621.             case INSTALLERROR_INVALID_VERSION:
  2622.               return "Invalid Version";
  2623.             case INSTALLERROR_INCOMPATIBLE_VERSION:
  2624.               return "Incompatible Version";
  2625.             case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  2626.               return "Incompatible Platform";
  2627.             }
  2628.           }
  2629.           LOG("... failure, item is not compatible, error: " + 
  2630.               translateErrorMessage(installData.error));
  2631.  
  2632.           // Add the item to the Startup Cache anyway, so we don't re-detect it
  2633.           // every time the app starts.
  2634.           StartupCache.put(location, id, OP_NONE, true);
  2635.         }
  2636.       }      
  2637.     }
  2638.   
  2639.     /**
  2640.      * Determines if an item can be used based on whether or not the install
  2641.      * location of the "item" has an equal or higher priority than the install
  2642.      * location where another version may live.
  2643.      * @param   id
  2644.      *          The GUID of the item being installed.
  2645.      * @param   location
  2646.      *          The location where an item is to be installed.
  2647.      * @returns true if the item can be installed at that location, false 
  2648.      *          otherwise.
  2649.      */
  2650.     function canUse(id, location) {
  2651.       for (var locationKey in StartupCache.entries) {
  2652.         if (locationKey != location.name && 
  2653.             id in StartupCache.entries[locationKey]) {
  2654.           if (StartupCache.entries[locationKey][id]) {
  2655.             var oldInstallLocation = InstallLocations.get(locationKey);
  2656.             if (oldInstallLocation.priority <= location.priority)
  2657.               return false;
  2658.           }
  2659.         }
  2660.       }
  2661.       return true;
  2662.     }
  2663.     
  2664.     /** 
  2665.       * Gets a Dialog Param Block loaded with a set of strings to initialize the
  2666.       * XPInstall Confirmation Dialog.
  2667.       * @param   strings
  2668.       *          An array of strings
  2669.       * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
  2670.       */
  2671.     function getParamBlock(strings) {
  2672.       var dpb = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  2673.                           .createInstance(Components.interfaces.nsIDialogParamBlock);
  2674.       // OK and Cancel Buttons
  2675.       dpb.SetInt(0, 2);
  2676.       // Number of Strings
  2677.       dpb.SetInt(1, strings.length);
  2678.       dpb.SetNumberStrings(strings.length);
  2679.       // Add Strings
  2680.       for (var i = 0; i < strings.length; ++i)
  2681.         dpb.SetString(i, strings[i]);
  2682.       
  2683.       var supportsString = Components.classes["@mozilla.org/supports-string;1"]
  2684.                                      .createInstance(Components.interfaces.nsISupportsString);
  2685.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  2686.       supportsString.data = bundle.GetStringFromName("droppedInWarning");
  2687.       var objs = Components.classes["@mozilla.org/array;1"]
  2688.                            .createInstance(Components.interfaces.nsIMutableArray);
  2689.       objs.appendElement(supportsString, false);
  2690.       dpb.objects = objs;
  2691.       return dpb;        
  2692.     }
  2693.  
  2694.     /**
  2695.      * Installs a set of files which were dropped into an install location by 
  2696.      * the user, only after user confirmation.
  2697.      * @param   droppedInFiles
  2698.      *          An array of JS objects with the following properties:
  2699.      *          "file"      The nsILocalFile where the XPI lives
  2700.      *          "location"  The Install Location where the XPI was found. 
  2701.      * @param   xpinstallStrings
  2702.      *          An array of strings used to initialize the XPInstall Confirm 
  2703.      *          dialog.
  2704.      */ 
  2705.     function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
  2706.       if (droppedInFiles.length == 0) 
  2707.         return;
  2708.         
  2709.       var dpb = getParamBlock(xpinstallStrings);
  2710.       var ifptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
  2711.                             .createInstance(Components.interfaces.nsISupportsInterfacePointer);
  2712.       ifptr.data = dpb;
  2713.       ifptr.dataIID = Components.interfaces.nsIDialogParamBlock;
  2714.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  2715.                           .getService(Components.interfaces.nsIWindowWatcher);
  2716.       ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG, 
  2717.                     "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
  2718.       if (!dpb.GetInt(0)) {
  2719.         // User said OK - install items
  2720.         for (var i = 0; i < droppedInFiles.length; ++i) {
  2721.           em.installItemFromFile(droppedInFiles[i].file, 
  2722.                                  droppedInFiles[i].location.name);
  2723.           // We are responsible for cleaning up this file
  2724.           droppedInFiles[i].file.remove(false);
  2725.         }
  2726.       }
  2727.       else {
  2728.         for (i = 0; i < droppedInFiles.length; ++i) {
  2729.           // We are responsible for cleaning up this file
  2730.           droppedInFiles[i].file.remove(false);
  2731.         }
  2732.       }
  2733.     }
  2734.     
  2735.     var isDirty = false;
  2736.     var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
  2737.                                      false);
  2738.     StartupCache.read();
  2739.     
  2740.     // Array of objects with 'location' and 'id' properties to maybe install.
  2741.     var newItems = [];
  2742.  
  2743.     var droppedInFiles = [];
  2744.     var xpinstallStrings = [];
  2745.     
  2746.     // Enumerate over the install locations from low to high priority.  The
  2747.     // enumeration returned is pre-sorted.
  2748.     var installLocations = this.installLocations;
  2749.     while (installLocations.hasMoreElements()) {
  2750.       var location = installLocations.getNext().QueryInterface(nsIInstallLocation);
  2751.  
  2752.       // Hash the set of items actually held by the Install Location.  
  2753.       var actualItems = { };
  2754.       var entries = location.itemLocations;
  2755.       while (true) {
  2756.         var entry = entries.nextFile;
  2757.         if (!entry)
  2758.           break;
  2759.  
  2760.         // Is this location a valid item? It must be a directory, and contain
  2761.         // an install.rdf manifest:
  2762.         if (entry.isDirectory()) {
  2763.           var installRDF = entry.clone();
  2764.           installRDF.append(FILE_INSTALL_MANIFEST);
  2765.  
  2766.           var id = location.getIDForLocation(entry);
  2767.           if (!id || (!installRDF.exists() && 
  2768.                       !location.itemIsManagedIndependently(id)))
  2769.             continue;
  2770.  
  2771.           actualItems[id] = entry;
  2772.         }
  2773.         else {
  2774.           // Check to see if this file is a XPI/JAR dropped into this dir
  2775.           // by the user, installing it if necessary. We do this here rather
  2776.           // than separately in |_finishOperations| because I don't want to
  2777.           // walk these lists multiple times on every startup.
  2778.           var item = this._getItemForDroppedFile(entry, location);
  2779.           if (item) {
  2780.             droppedInFiles.push({ file: entry, location: location });
  2781.  
  2782.             var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  2783.                                       .createInstance(Components.interfaces.nsIZipReader);
  2784.             zipReader.init(entry);
  2785.             var prettyName = "";
  2786.             try {
  2787.               var jar = zipReader.QueryInterface(Components.interfaces.nsIJAR);
  2788.               var principal = { };
  2789.               var certPrincipal = zipReader.getCertificatePrincipal(null, principal);
  2790.               // XXXbz This string could be empty.  This needs better
  2791.               // UI to present principal.value.certificate's subject.
  2792.               prettyName = principal.value.prettyName;
  2793.             }
  2794.             catch (e) { }
  2795.             xpinstallStrings = xpinstallStrings.concat([item.name, 
  2796.                                                         getURLSpecFromFile(entry),
  2797.                                                         item.iconURL, 
  2798.                                                         prettyName]);
  2799.             isDirty = true;
  2800.           }
  2801.         }
  2802.       }
  2803.       
  2804.       if (location.name in StartupCache.entries) {
  2805.         // Look for items that have been uninstalled by removing their directory.
  2806.         for (var id in StartupCache.entries[location.name]) {
  2807.           if (!StartupCache.entries[location.name] ||
  2808.               !StartupCache.entries[location.name][id]) 
  2809.             continue;
  2810.  
  2811.           // Force _finishOperations to run if we have enabled or disabled items.
  2812.           // XXXdarin this should be unnecessary now that we check
  2813.           // PendingOperations.size in start()
  2814.           if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
  2815.               StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
  2816.             isDirty = true;
  2817.           
  2818.           if (!(id in actualItems) && 
  2819.               StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL &&
  2820.               StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
  2821.               StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
  2822.             // We have an entry for this id in the Extensions database, for this 
  2823.             // install location, but it no longer exists in the Install Location. 
  2824.             // We can infer from this that the item has been removed, so uninstall
  2825.             // it properly. 
  2826.             if (canUse(id, location)) {
  2827.               LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor + 
  2828.                   " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
  2829.               
  2830.               // Load the Extensions Datasource and force this item into the visible
  2831.               // items list if it is not already. This allows us to handle the case 
  2832.               // where there is an entry for an item in the Startup Cache but not
  2833.               // in the extensions.rdf file - in that case the item will not be in
  2834.               // the visible list and calls to |getInstallLocation| will mysteriously
  2835.               // fail.
  2836.               this.datasource.updateVisibleList(id, location.name, false);
  2837.               this.uninstallItem(id);
  2838.               isDirty = true;
  2839.             }
  2840.           }
  2841.           else if (!ignoreMTimeChanges) {
  2842.             // Look for items whose mtime has changed, and as such we can assume 
  2843.             // they have been "upgraded".
  2844.             var lf = { path: StartupCache.entries[location.name][id].descriptor };
  2845.             try {
  2846.                lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
  2847.             }
  2848.             catch (e) { }
  2849.  
  2850.             if (lf.exists && lf.exists()) {
  2851.               var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
  2852.               if (actualMTime != StartupCache.entries[location.name][id].mtime) {
  2853.                 LOG("Item Location path changed: " + lf.path + " Item ID: " + 
  2854.                     id + " Location Key: " + location.name + ", attempting to upgrade item...");
  2855.                 if (canUse(id, location)) {
  2856.                   installItem(id, location, 
  2857.                               function(installManifest, id, location, type) {
  2858.                                 em._upgradeItem(installManifest, id, location, 
  2859.                                                 type);
  2860.                               });
  2861.                   isDirty = true;
  2862.                 }
  2863.               }
  2864.             }
  2865.             else {
  2866.               isDirty = true;
  2867.               LOG("Install Location returned a missing or malformed item path! " + 
  2868.                   "Item Path: " + lf.path + ", Location Key: " + location.name + 
  2869.                   " Item ID: " + id);
  2870.               if (canUse(id, location)) {
  2871.                 // Load the Extensions Datasource and force this item into the visible
  2872.                 // items list if it is not already. This allows us to handle the case 
  2873.                 // where there is an entry for an item in the Startup Cache but not
  2874.                 // in the extensions.rdf file - in that case the item will not be in
  2875.                 // the visible list and calls to |getInstallLocation| will mysteriously
  2876.                 // fail.
  2877.                 this.datasource.updateVisibleList(id, location.name, false);
  2878.                 this.uninstallItem(id);
  2879.               }
  2880.             }
  2881.           }
  2882.         }
  2883.       }
  2884.  
  2885.       // Look for items that have been installed by appearing in the location.
  2886.       for (var id in actualItems) {
  2887.         if (!(location.name in StartupCache.entries) || 
  2888.             !(id in StartupCache.entries[location.name]) ||
  2889.             !StartupCache.entries[location.name][id]) {
  2890.           // Remember that we've seen this item
  2891.           StartupCache.put(location, id, OP_NONE, true);
  2892.           // Push it on the stack of items to maybe install later
  2893.           newItems.push({location: location, id: id});
  2894.         }
  2895.       }
  2896.     }
  2897.  
  2898.     // Process any newly discovered items.  We do this here instead of in the
  2899.     // previous loop so that we can be sure that we have a fully populated
  2900.     // StartupCache.
  2901.     for (var i = 0; i < newItems.length; ++i) {
  2902.       var id = newItems[i].id;
  2903.       var location = newItems[i].location;
  2904.       if (canUse(id, location)) {
  2905.         LOG("Item Installed via directory addition to Install Location: " + 
  2906.             location.name + " Item ID: " + id + ", attempting to register...");
  2907.         installItem(id, location, 
  2908.                     function(installManifest, id, location, type) { 
  2909.                       em._configureForthcomingItem(installManifest, id, location, 
  2910.                                                    type);
  2911.                     });
  2912.         isDirty = true;
  2913.       }
  2914.     }
  2915.  
  2916.     // Ask the user if they want to install the dropped items, for security
  2917.     // purposes.
  2918.     installDroppedInFiles(droppedInFiles, xpinstallStrings);
  2919.     
  2920.     return isDirty;
  2921.   },
  2922.   
  2923.   /**
  2924.    * Upgrades contents.rdf files to chrome.manifest files for any existing
  2925.    * Extensions and Themes.
  2926.    * @returns true if actions were performed that require a restart, false 
  2927.    *          otherwise.
  2928.    */
  2929.   _upgradeChrome: function() {
  2930.     if (inSafeMode())
  2931.       return false;
  2932.  
  2933.     var checkForNewChrome = false;
  2934.     var ds = this.datasource;
  2935.     // If we have extensions that were installed before the new flat chrome
  2936.     // manifests, and are still valid, we need to manually create the flat
  2937.     // manifest files.
  2938.     var extensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  2939.     for (var i = 0; i < extensions.length; ++i) {
  2940.       var e = extensions[i];
  2941.       var itemLocation = e.location.getItemLocation(e.id);
  2942.       var manifest = itemLocation.clone();
  2943.       manifest.append(FILE_CHROME_MANIFEST);
  2944.       if (!manifest.exists()) {
  2945.         var installRDF = itemLocation.clone();
  2946.         installRDF.append(FILE_INSTALL_MANIFEST);
  2947.         var installLocation = this.getInstallLocation(e.id);
  2948.         if (installLocation && installRDF.exists()) {
  2949.           var itemLocation = installLocation.getItemLocation(e.id);
  2950.           if (itemLocation.exists() && itemLocation.isDirectory()) {
  2951.             var installer = new Installer(ds, e.id, installLocation, 
  2952.                                           nsIUpdateItem.TYPE_EXTENSION);
  2953.             installer.upgradeExtensionChrome();
  2954.           }
  2955.         }
  2956.         else {
  2957.           ds.removeItemMetadata(e.id);
  2958.           ds.removeItemFromContainer(e.id);
  2959.         }
  2960.  
  2961.         checkForNewChrome = true;
  2962.       }
  2963.     }
  2964.  
  2965.     var themes = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  2966.     // If we have themes that were installed before the new flat chrome
  2967.     // manifests, and are still valid, we need to manually create the flat
  2968.     // manifest files.
  2969.     for (i = 0; i < themes.length; ++i) {
  2970.       var item = themes[i];
  2971.       var itemLocation = item.location.getItemLocation(item.id);
  2972.       var manifest = itemLocation.clone();
  2973.       manifest.append(FILE_CHROME_MANIFEST);
  2974.       if (manifest.exists() ||
  2975.           item.id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  2976.         continue;
  2977.  
  2978.       var entries;
  2979.       try {
  2980.         var manifestURI = getURIFromFile(manifest);
  2981.         var chromeDir = itemLocation.clone();
  2982.         chromeDir.append(DIR_CHROME);
  2983.         
  2984.         if (!chromeDir.exists() || !chromeDir.isDirectory()) {
  2985.           ds.removeItemMetadata(item.id);
  2986.           ds.removeItemFromContainer(item.id);
  2987.           continue;
  2988.         }
  2989.  
  2990.         // We're relying on the fact that there is only one JAR file
  2991.         // in the "chrome" directory. This is a hack, but it works.
  2992.         entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  2993.         var jarFile = entries.nextFile;
  2994.         if (jarFile) {
  2995.           var jarFileURI = getURIFromFile(jarFile);
  2996.           var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  2997.  
  2998.           // Use the Chrome Registry API to install the theme there
  2999.           var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  3000.                             .getService(Components.interfaces.nsIToolkitChromeRegistry);
  3001.           cr.processContentsManifest(contentsURI, manifestURI, contentsURI, false, true);
  3002.         }
  3003.         entries.close();
  3004.       }
  3005.       catch (e) {
  3006.         LOG("_upgradeChrome: failed to upgrade contents manifest for " + 
  3007.             "theme: " + item.id + ", exception: " + e + "... The theme will be " + 
  3008.             "disabled.");
  3009.         this._appDisableItem(item.id);
  3010.       }
  3011.       finally {
  3012.         try {
  3013.           entries.close();
  3014.         }
  3015.         catch (e) {
  3016.         }
  3017.       }
  3018.       checkForNewChrome = true;
  3019.     }
  3020.     return checkForNewChrome;  
  3021.   },
  3022.   
  3023.   _checkForUncoveredItem: function(id) {
  3024.     var ds = this.datasource;
  3025.     var oldLocation = this.getInstallLocation(id);
  3026.     var newLocations = [];
  3027.     for (var locationKey in StartupCache.entries) {
  3028.       var location = InstallLocations.get(locationKey);
  3029.       if (id in StartupCache.entries[locationKey] && 
  3030.           location.priority > oldLocation.priority)
  3031.         newLocations.push(location);
  3032.     }
  3033.     newLocations.sort(function(a, b) { return b.priority - a.priority; });
  3034.     if (newLocations.length > 0) {
  3035.       for (var i = 0; i < newLocations.length; ++i) {
  3036.         // Check to see that the item at the location exists
  3037.         var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
  3038.         if (installRDF.exists()) {
  3039.           // Update the visible item cache so that |_finalizeUpgrade| is properly 
  3040.           // called from |_finishOperations|
  3041.           var name = newLocations[i].name;
  3042.           ds.updateVisibleList(id, name, true);
  3043.           PendingOperations.addItem(OP_NEEDS_UPGRADE, 
  3044.                                     { locationKey: name, id: id });
  3045.           PendingOperations.addItem(OP_NEEDS_INSTALL, 
  3046.                                     { locationKey: name, id: id });
  3047.           break;
  3048.         }
  3049.         else {
  3050.           // If no item exists at the location specified, remove this item
  3051.           // from the visible items list and check again. 
  3052.           StartupCache.clearEntry(newLocations[i], id);
  3053.           ds.updateVisibleList(id, null, true);
  3054.         }
  3055.       }
  3056.     }
  3057.     else
  3058.       ds.updateVisibleList(id, null, true);
  3059.   },
  3060.   
  3061.   /**
  3062.    * Finish up pending operations - perform upgrades, installs, enables/disables, 
  3063.    * uninstalls etc.
  3064.    * @returns true if actions were performed that require a restart, false 
  3065.    *          otherwise.
  3066.    */
  3067.   _finishOperations: function() {
  3068.     try {
  3069.       // Stuff has changed, load the Extensions datasource in all its RDFey
  3070.       // glory. 
  3071.       var ds = this.datasource;
  3072.       var updatedTargetAppInfos = [];
  3073.  
  3074.       var needsRestart = false;      
  3075.       do {
  3076.         // Enable and disable during startup so items that are changed in the
  3077.         // ui can be reset to a no-op.
  3078.         // Look for extensions that need to be enabled.
  3079.         var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
  3080.         for (var i = items.length - 1; i >= 0; --i) {
  3081.           var id = items[0].id;
  3082.           ds.setItemProperty(id, EM_R("userDisabled"), null);
  3083.           ds.setItemProperty(id, EM_R("appDisabled"), null);
  3084.           var installLocation = this.getInstallLocation(id);
  3085.           StartupCache.put(installLocation, id, OP_NONE, true);
  3086.           PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
  3087.           needsRestart = true;
  3088.         }
  3089.         PendingOperations.clearItems(OP_NEEDS_ENABLE);
  3090.  
  3091.         // Look for extensions that need to be disabled.
  3092.         items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
  3093.         for (i = items.length - 1; i >= 0; --i) {
  3094.           id = items[i].id;
  3095.           // Only set the userDisabled property when the item's appDisabled
  3096.           // property is not true since an item can't be disabled by the user
  3097.           // when it is already disabled by the application.
  3098.           if (ds.getItemProperty(id, "appDisabled") != "true")
  3099.             ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  3100.           installLocation = this.getInstallLocation(id);
  3101.           StartupCache.put(installLocation, id, OP_NONE, true);
  3102.           PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
  3103.         }
  3104.         PendingOperations.clearItems(OP_NEEDS_DISABLE);
  3105.  
  3106.         // Look for extensions that need to be upgraded. The process here is to
  3107.         // uninstall the old version of the extension first, then install the
  3108.         // new version in its place. 
  3109.         items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  3110.         for (i = items.length - 1; i >= 0; --i) {
  3111.           id = items[i].id;
  3112.           var oldLocation = this.getInstallLocation(id);
  3113.           var newLocation = InstallLocations.get(items[i].locationKey);
  3114.           if (newLocation.priority <= oldLocation.priority) {
  3115.             // check if there is updated app compatibility info
  3116.             var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3117.             if (newTargetAppInfo)
  3118.               updatedTargetAppInfos.push(newTargetAppInfo);
  3119.             this._finalizeUpgrade(id);
  3120.           }
  3121.         }
  3122.         PendingOperations.clearItems(OP_NEEDS_UPGRADE);
  3123.  
  3124.         // Install items
  3125.         items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3126.         for (i = items.length - 1; i >= 0; --i) {
  3127.           needsRestart = true;
  3128.           id = items[i].id;
  3129.           // check if there is updated app compatibility info
  3130.           newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3131.           if (newTargetAppInfo)
  3132.             updatedTargetAppInfos.push(newTargetAppInfo);
  3133.           this._finalizeInstall(id, null);
  3134.         }
  3135.         PendingOperations.clearItems(OP_NEEDS_INSTALL);
  3136.  
  3137.         // Look for extensions that need to be removed. This MUST be done after
  3138.         // the install operations since extensions to be installed may have to be
  3139.         // uninstalled if there are errors during the installation process!
  3140.         items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
  3141.         for (i = items.length - 1; i >= 0; --i) {
  3142.           id = items[i].id;
  3143.           this._finalizeUninstall(id);
  3144.           this._checkForUncoveredItem(id);
  3145.           needsRestart = true;
  3146.         }
  3147.         PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
  3148.       }
  3149.       while (PendingOperations.size > 0);
  3150.       
  3151.       // Upgrade contents.rdf files to the new chrome.manifest format for
  3152.       // existing Extensions and Themes
  3153.       if (this._upgradeChrome()) {
  3154.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  3155.                            .getService(Components.interfaces.nsIChromeRegistry);
  3156.         cr.checkForNewChrome();
  3157.       }      
  3158.  
  3159.       // If no additional restart is required, it implies that there are
  3160.       // no new components that need registering so we can inform the app
  3161.       // not to do any extra startup checking next time round. 
  3162.       this._updateManifests(needsRestart);
  3163.  
  3164.       // If there is updated app compatibility info update the data sources.
  3165.       for (i = 0; i < updatedTargetAppInfos.length; ++i) {
  3166.         ds.updateTargetAppInfo(updatedTargetAppInfos[i].id,
  3167.                                updatedTargetAppInfos[i].minVersion,
  3168.                                updatedTargetAppInfos[i].maxVersion);
  3169.       }
  3170.     }
  3171.     catch (e) {
  3172.       LOG("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
  3173.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  3174.     }
  3175.     return needsRestart;
  3176.   },
  3177.   
  3178.   /**
  3179.    * Checks to see if there are items that are incompatible with this version
  3180.    * of the application, disables them to prevent incompatibility problems and 
  3181.    * invokes the Update Wizard to look for newer versions.
  3182.    * @returns true if there were incompatible items installed and disabled, and
  3183.    *          the application must now be restarted to reinitialize XPCOM,
  3184.    *          false otherwise.
  3185.    */
  3186.   checkForMismatches: function() {
  3187.     // Check to see if the version of the application that is being started
  3188.     // now is the same one that was started last time. 
  3189.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  3190.                                  gApp.version);
  3191.     var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
  3192.     if (currAppVersion == lastAppVersion)
  3193.       return false;
  3194.     // With a new profile lastAppVersion doesn't exist yet.
  3195.     if (!lastAppVersion) {
  3196.       gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3197.       return false;
  3198.     }
  3199.  
  3200.     // Version mismatch, we have to load the extensions datasource and do
  3201.     // version checking. Time hit here doesn't matter since this doesn't happen
  3202.     // all that often.
  3203.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  3204.     if (!extensionsDS.exists())
  3205.       this._upgradeFromV10();
  3206.     
  3207.     // Make the extensions datasource consistent if it isn't already.
  3208.     var isDirty = false;
  3209.     if (this._ensureDatasetIntegrity())
  3210.       isDirty = true;
  3211.  
  3212.     if (this._checkForFileChanges())
  3213.       isDirty = true;
  3214.  
  3215.     if (PendingOperations.size != 0)
  3216.       isDirty = true;
  3217.  
  3218.     if (isDirty)
  3219.       this._finishOperations();
  3220.  
  3221.     // Disable all incompatible items and let update enable them if appropriate.
  3222.     var ds = this.datasource;
  3223.     var currAppID = gApp.ID;
  3224.     var items = ds.getIncompatibleItemList(currAppID, currAppVersion,
  3225.                                            nsIUpdateItem.TYPE_ADDON, true);
  3226.     for (var i = 0; i < items.length; ++i)
  3227.       ds.setItemProperty(items[i].id, EM_R("appDisabled"), EM_L("true"));
  3228.     // Update the manifests to reflect the items that were disabled.
  3229.     this._updateManifests(true);
  3230.  
  3231.     // Always check for compatibility updates when upgrading
  3232.     this._showMismatchWindow();
  3233.     
  3234.     // Finish any pending upgrades from the compatibility update to avoid an
  3235.     // additional restart.
  3236.     if (PendingOperations.size != 0)
  3237.       this._finishOperations();
  3238.  
  3239.     // Update the last app version so we don't do this again with this version.
  3240.     gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3241.  
  3242.     return true;
  3243.   },
  3244.  
  3245.   /**
  3246.    * Shows the "Compatibility Updates" UI
  3247.    */
  3248.   _showMismatchWindow: function(items) {
  3249.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3250.                        .getService(Components.interfaces.nsIWindowMediator);
  3251.     var wizard = wm.getMostRecentWindow("Update:Wizard");
  3252.     if (wizard)
  3253.       wizard.focus();
  3254.     else {
  3255.       var features = "chrome,centerscreen,dialog,titlebar,modal";
  3256.       // This *must* be modal so as not to break startup! This code is invoked before
  3257.       // the main event loop is initiated (via checkForMismatches).
  3258.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3259.                          .getService(Components.interfaces.nsIWindowWatcher);
  3260.       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, null);
  3261.     }
  3262.   },
  3263.   
  3264.   /*
  3265.    * Catch all for facilitating a version 1.0 profile upgrade.
  3266.    * 1) removes the abandoned default theme directory from the profile.
  3267.    * 2) prepares themes installed with version 1.0 for installation.
  3268.    * 3) initiates an install to populate the new extensions datasource.
  3269.    * 4) migrates the disabled attribute from the old datasource.
  3270.    * 5) migrates the app compatibility info from the old datasource.
  3271.    */
  3272.   _upgradeFromV10: function() {
  3273.     // return early if the version 1.0 extensions datasource file doesn't exist.
  3274.     var oldExtensionsFile = getFile(KEY_PROFILEDIR, [DIR_EXTENSIONS, "Extensions.rdf"]);
  3275.     if (!oldExtensionsFile.exists())
  3276.       return;
  3277.  
  3278.     // Version 1.0 profiles have a default theme directory in the profile's
  3279.     // extensions directory that will be disabled due to having a maxVersion
  3280.     // of 1.0 so we must remove it if it exists.
  3281.     var profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3282.                                              stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)]);
  3283.     if (profileDefaultTheme && profileDefaultTheme.exists())
  3284.       removeDirRecursive(profileDefaultTheme);
  3285.  
  3286.     // Version 0.9 profiles may have DOMi 1.0 with just an install.rdf
  3287.     var profileDOMi = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3288.                                      "{641d8d09-7dda-4850-8228-ac0ab65e2ac9}"]);
  3289.     if (profileDOMi && profileDOMi.exists())
  3290.       removeDirRecursive(profileDOMi);
  3291.  
  3292.     // Prepare themes for installation
  3293.     // Only enumerate directories in the app-profile and app-global locations.
  3294.     var locations = [KEY_APP_PROFILE, KEY_APP_GLOBAL];
  3295.     for (var i = 0; i < locations.length; ++i) {
  3296.       var location = InstallLocations.get(locations[i]);
  3297.       if (!location.canAccess)
  3298.         continue;
  3299.  
  3300.       var entries = location.itemLocations;
  3301.       var entry;
  3302.       while ((entry = entries.nextFile)) {
  3303.         var installRDF = entry.clone();
  3304.         installRDF.append(FILE_INSTALL_MANIFEST);
  3305.  
  3306.         var chromeDir = entry.clone();
  3307.         chromeDir.append(DIR_CHROME);
  3308.  
  3309.         // It must be a directory without an install.rdf and it must contain
  3310.         // a chrome directory
  3311.         if (!entry.isDirectory() || installRDF.exists() || !chromeDir.exists())
  3312.           continue;
  3313.  
  3314.         var chromeEntries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  3315.         if (!chromeEntries.hasMoreElements())
  3316.           continue;
  3317.  
  3318.         // We're relying on the fact that there is only one JAR file
  3319.         // in the "chrome" directory. This is a hack, but it works.
  3320.         var jarFile = chromeEntries.nextFile;
  3321.         var id = location.getIDForLocation(entry);
  3322.  
  3323.         try {
  3324.           var zipReader = getZipReaderForFile(jarFile);
  3325.           zipReader.extract(FILE_INSTALL_MANIFEST, installRDF);
  3326.  
  3327.           var contentsManifestFile = location.getItemFile(id, FILE_CONTENTS_MANIFEST);
  3328.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  3329.  
  3330.           var rootFiles = ["preview.png", "icon.png"];
  3331.           for (var i = 0; i < rootFiles.length; ++i) {
  3332.             try {
  3333.               var target = location.getItemFile(id, rootFiles[i]);
  3334.               zipReader.extract(rootFiles[i], target);
  3335.             }
  3336.             catch (e) {
  3337.             }
  3338.           }
  3339.         }
  3340.         catch (e) {
  3341.           LOG("ExtensionManager:_upgradeFromV10 - failed to extract theme files\r\n" +
  3342.               "Exception: " + e);
  3343.         }
  3344.         zipReader.close();
  3345.       }
  3346.     }
  3347.  
  3348.     // When upgrading from a version 1.0 profile we need to populate the
  3349.     // extensions datasource with all items before checking for incompatible
  3350.     // items since the datasource hasn't been created yet.
  3351.     var itemsToCheck = [];
  3352.     if (this._checkForFileChanges()) {
  3353.       // Create a list of all items that are to be installed so we can migrate
  3354.       // these items's settings to the new datasource.
  3355.       var items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3356.       for (i = items.length - 1; i >= 0; --i) {
  3357.         if (items[i].locationKey == KEY_APP_PROFILE ||
  3358.             items[i].locationKey == KEY_APP_GLOBAL)
  3359.           itemsToCheck.push(items[i].id);
  3360.       }
  3361.       this._finishOperations();
  3362.     }
  3363.  
  3364.     // If there are no items to migrate settings for return early.
  3365.     if (itemsToCheck.length == 0)
  3366.       return;
  3367.  
  3368.     var fileURL = getURLSpecFromFile(oldExtensionsFile);
  3369.     var oldExtensionsDS = gRDF.GetDataSourceBlocking(fileURL);
  3370.     var versionChecker = getVersionChecker();
  3371.     var ds = this.datasource;
  3372.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  3373.                                  gApp.version);
  3374.     var currAppID = gApp.ID;
  3375.     for (var i = 0; i < itemsToCheck.length; ++i) {
  3376.       var item = ds.getItemForID(itemsToCheck[i]);
  3377.       var oldPrefix = (item.type == nsIUpdateItem.TYPE_EXTENSION) ? PREFIX_EXTENSION : PREFIX_THEME;
  3378.       var oldRes = gRDF.GetResource(oldPrefix + item.id);
  3379.       // Disable the item if it was disabled in the version 1.0 extensions
  3380.       // datasource.
  3381.       if (oldExtensionsDS.GetTarget(oldRes, EM_R("disabled"), true))
  3382.         ds.setItemProperty(item.id, EM_R("userDisabled"), EM_L("true"));
  3383.  
  3384.       // app enable all items. If it is incompatible it will be app disabled
  3385.       // later on.
  3386.       ds.setItemProperty(item.id, EM_R("appDisabled"), null);
  3387.  
  3388.       // if the item is already compatible don't attempt to migrate the
  3389.       // item's compatibility info
  3390.       var newRes = getResourceForID(itemsToCheck[i]);
  3391.       if (ds.isCompatible(ds, newRes))
  3392.         continue;
  3393.  
  3394.       var updatedMinVersion = null;
  3395.       var updatedMaxVersion = null;
  3396.       var targetApps = oldExtensionsDS.GetTargets(oldRes, EM_R("targetApplication"), true);
  3397.       while (targetApps.hasMoreElements()) {
  3398.         var targetApp = targetApps.getNext();
  3399.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3400.           try {
  3401.             var foundAppID = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("id"), true));
  3402.             if (foundAppID != currAppID) // Different target application
  3403.               continue;
  3404.  
  3405.             updatedMinVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("minVersion"), true));
  3406.             updatedMaxVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("maxVersion"), true));
  3407.  
  3408.             // Only set the target app info if the extension's target app info
  3409.             // in the version 1.0 extensions datasource makes it compatible
  3410.             if (versionChecker.compare(currAppVersion, updatedMinVersion) >= 0 &&
  3411.                 versionChecker.compare(currAppVersion, updatedMaxVersion) <= 0)
  3412.               ds.updateTargetAppInfo(item.id, updatedMinVersion, updatedMaxVersion);
  3413.  
  3414.             break;
  3415.           }
  3416.           catch (e) { 
  3417.           }
  3418.         }
  3419.       }
  3420.     }
  3421.   },
  3422.  
  3423.   /**
  3424.    * Write the Extensions List and the Startup Cache
  3425.    * @param   needsRestart
  3426.    *          true if the application needs to restart again, false otherwise.
  3427.    */  
  3428.   _updateManifests: function(needsRestart) {
  3429.     // Write the Startup Cache (All Items, visible or not)
  3430.     StartupCache.write();
  3431.     // Write the Extensions Locations Manifest (Visible, enabled items)
  3432.     this._updateExtensionsManifest(needsRestart);
  3433.   },
  3434.  
  3435.   /**
  3436.    * Get a list of items that are currently "active" (turned on) of a specific
  3437.    * type
  3438.    * @param   type
  3439.    *          The nsIUpdateItem type to return a list of items of
  3440.    * @returns An array of active items of the specified type.
  3441.    */
  3442.   _getActiveItems: function(type) {
  3443.     var allItems = this.getItemList(type, { });
  3444.     var activeItems = [];
  3445.     var ds = this.datasource;
  3446.     for (var i = 0; i < allItems.length; ++i) {
  3447.       var item = allItems[i];
  3448.  
  3449.       // An item entry is valid only if it is not disabled, not about to 
  3450.       // be disabled, and not about to be uninstalled.
  3451.       var installLocation = this.getInstallLocation(item.id);
  3452.       if (installLocation.name in StartupCache.entries &&
  3453.           item.id in StartupCache.entries[installLocation.name] &&
  3454.           StartupCache.entries[installLocation.name][item.id]) {
  3455.         var op = StartupCache.entries[installLocation.name][item.id].op;
  3456.         if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE || 
  3457.             op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
  3458.           continue;
  3459.       }
  3460.       // Suppress items that have been disabled by the user or the app.
  3461.       if (ds.getItemProperty(item.id, "disabled") != "true")
  3462.         activeItems.push({ id: item.id, location: installLocation });
  3463.     }
  3464.  
  3465.     return activeItems;
  3466.   },
  3467.   
  3468.   /**
  3469.    * Write the Extensions List
  3470.    * @param   needsRestart
  3471.    *          true if the application needs to restart again, false otherwise.
  3472.    */
  3473.   _updateExtensionsManifest: function(needsRestart) {
  3474.     // When an operation is performed that requires a component re-registration
  3475.     // (extension enabled/disabled, installed, uninstalled), we must write the
  3476.     // set of paths where extensions live so that the startup system can determine
  3477.     // where additional components, preferences, chrome manifests etc live.
  3478.     //
  3479.     // To do this we obtain a list of active extensions and themes and write 
  3480.     // these to the extensions.ini file in the profile directory.
  3481.     var validExtensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  3482.     var validThemes     = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  3483.  
  3484.     var extensionsLocationsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  3485.     var fos = openSafeFileOutputStream(extensionsLocationsFile);
  3486.         
  3487.     var extensionSectionHeader = "[ExtensionDirs]\r\n";
  3488.     fos.write(extensionSectionHeader, extensionSectionHeader.length);
  3489.     for (i = 0; i < validExtensions.length; ++i) {
  3490.       var e = validExtensions[i];
  3491.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3492.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3493.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3494.       fos.write(line, line.length);
  3495.     }
  3496.  
  3497.     var themeSectionHeader = "[ThemeDirs]\r\n";
  3498.     fos.write(themeSectionHeader, themeSectionHeader.length);
  3499.     for (i = 0; i < validThemes.length; ++i) {
  3500.       var e = validThemes[i];
  3501.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3502.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3503.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3504.       fos.write(line, line.length);
  3505.     }
  3506.  
  3507.     closeSafeFileOutputStream(fos);
  3508.  
  3509.     // Now refresh the compatibility manifest.
  3510.     this._extensionListChanged = needsRestart;
  3511.   },
  3512.   
  3513.   /**
  3514.    * Say whether or not the Extension List has changed (and thus whether or not
  3515.    * the system will have to restart the next time it is started).
  3516.    * @param   val
  3517.    *          true if the Extension List has changed, false otherwise.
  3518.    * @returns |val|
  3519.    */
  3520.   set _extensionListChanged(val) {
  3521.     // When an extension has an operation perform on it (e.g. install, upgrade,
  3522.     // disable, etc.) we are responsible for creating the .autoreg file and
  3523.     // nsAppRunner is responsible for removing it on restart. At some point it
  3524.     // may make sense to be able to cancel a registration but for now we only
  3525.     // create the file.
  3526.     try {
  3527.       var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
  3528.       if (val && !autoregFile.exists())
  3529.         autoregFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3530.     }
  3531.     catch (e) {
  3532.     }
  3533.     return val;
  3534.   },
  3535.   
  3536.   /**
  3537.    * Gathers data about an item specified by the supplied Install Manifest
  3538.    * and determines whether or not it can be installed as-is. It makes this 
  3539.    * determination by validating the item's GUID, Version, and determining 
  3540.    * if it is compatible with this application.
  3541.    * @param   installManifest 
  3542.    *          A nsIRDFDataSource representing the Install Manifest of the 
  3543.    *          item to be installed.
  3544.    * @return  A JS Object with the following properties:
  3545.    *          "id"       The GUID of the Item being installed.
  3546.    *          "version"  The Version string of the Item being installed.
  3547.    *          "name"     The Name of the Item being installed.
  3548.    *          "type"     The nsIUpdateItem type of the Item being installed.
  3549.    *          "targetApps" An array of TargetApplication Info Objects
  3550.    *                     with "id", "minVersion" and "maxVersion" properties,
  3551.    *                     representing applications targeted by this item.
  3552.    *          "error"    The result code:
  3553.    *                     INSTALLERROR_SUCCESS      
  3554.    *                       no error, item can be installed
  3555.    *                     INSTALLERROR_INVALID_GUID 
  3556.    *                       error, GUID is not well-formed
  3557.    *                     INSTALLERROR_INVALID_VERSION
  3558.    *                       error, Version is not well-formed
  3559.    *                     INSTALLERROR_INCOMPATIBLE_VERSION
  3560.    *                       error, item is not compatible with this version
  3561.    *                       of the application.
  3562.    *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
  3563.    *                       error, item is not compatible with the operating
  3564.    *                       system or ABI the application was built for.
  3565.    */
  3566.   _getInstallData: function(installManifest) {
  3567.     var installData = { id          : "", 
  3568.                         version     : "", 
  3569.                         name        : "", 
  3570.                         type        : 0, 
  3571.                         error       : INSTALLERROR_SUCCESS, 
  3572.                         targetApps  : [],
  3573.                         currentApp  : null };
  3574.  
  3575.     // Fetch properties from the Install Manifest
  3576.     installData.id       = getManifestProperty(installManifest, "id");
  3577.     installData.version  = getManifestProperty(installManifest, "version");
  3578.     installData.name     = getManifestProperty(installManifest, "name");
  3579.     installData.type     = getAddonTypeFromInstallManifest(installManifest);
  3580.     installData.updateURL= getManifestProperty(installManifest, "updateURL");
  3581.  
  3582.     /**
  3583.      * Reads a property off a Target Application resource
  3584.      * @param   resource
  3585.      *          The RDF Resource for a Target Application
  3586.      * @param   property
  3587.      *          The property (less EM_NS) to read
  3588.      * @returns The string literal value of the property.
  3589.      */
  3590.     function readTAProperty(resource, property) {
  3591.       return stringData(installManifest.GetTarget(resource, EM_R(property), true));
  3592.     }
  3593.     
  3594.     var targetApps = installManifest.GetTargets(gInstallManifestRoot, 
  3595.                                                 EM_R("targetApplication"), 
  3596.                                                 true);
  3597.     while (targetApps.hasMoreElements()) {
  3598.       var targetApp = targetApps.getNext();
  3599.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3600.         try {
  3601.           var data = { id        : readTAProperty(targetApp, "id"),
  3602.                        minVersion: readTAProperty(targetApp, "minVersion"),
  3603.                        maxVersion: readTAProperty(targetApp, "maxVersion") };
  3604.           installData.targetApps.push(data);
  3605.           if (data.id == gApp.ID) 
  3606.             installData.currentApp = data;
  3607.         }
  3608.         catch (e) {
  3609.           continue;
  3610.         }
  3611.       }
  3612.     }
  3613.  
  3614.     // If the item specifies one or more target platforms, make sure our OS/ABI
  3615.     // combination is in the list - otherwise, refuse to install the item.
  3616.     var targetPlatforms = null;
  3617.     try {
  3618.       targetPlatforms = installManifest.GetTargets(gInstallManifestRoot, 
  3619.                                                    EM_R("targetPlatform"), 
  3620.                                                    true);
  3621.     } catch(e) {
  3622.       // No targetPlatform nodes, continue.
  3623.     }
  3624.     if (targetPlatforms != null && targetPlatforms.hasMoreElements()) {
  3625.       var foundMatchingOS = false;
  3626.       var foundMatchingOSAndABI = false;
  3627.       var requireABICompatibility = false;
  3628.       while (targetPlatforms.hasMoreElements()) {
  3629.         var targetPlatform = stringData(targetPlatforms.getNext());
  3630.         var tokens = targetPlatform.split("_");
  3631.         var os = tokens[0];
  3632.         var abi = (tokens.length > 1) ? tokens[1] : null;
  3633.         if (os == gOSTarget) {
  3634.           foundMatchingOS = true;
  3635.           // The presence of any ABI part after our OS means ABI is important.
  3636.           if (abi != null) {
  3637.             requireABICompatibility = true;
  3638.             // If we don't know our ABI, we can't be compatible
  3639.             if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) {
  3640.               foundMatchingOSAndABI = true;
  3641.               break;
  3642.             }
  3643.           }
  3644.         }
  3645.       }
  3646.       if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) {
  3647.         installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM;
  3648.         return installData;
  3649.       }
  3650.     }
  3651.  
  3652.     // Validate the Item ID
  3653.     if (!gIDTest.test(installData.id)) {
  3654.       installData.error = INSTALLERROR_INVALID_GUID;
  3655.       return installData;
  3656.     }
  3657.      
  3658.     // Check the target application range specified by the extension metadata.
  3659.     if (!this.datasource.isCompatible(installManifest, gInstallManifestRoot, undefined))
  3660.       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
  3661.     
  3662.     return installData;
  3663.   },  
  3664.   
  3665.   /**
  3666.    * Installs an item from a XPI/JAR file. 
  3667.    * This is the main entry point into the Install system from outside code
  3668.    * (e.g. XPInstall).
  3669.    * @param   aXPIFile
  3670.    *          The file to install from.
  3671.    * @param   aInstallLocationKey
  3672.    *          The name of the Install Location where this item should be 
  3673.    *          installed.
  3674.    */  
  3675.   installItemFromFile: function(xpiFile, installLocationKey) {
  3676.     this.installItemFromFileInternal(xpiFile, installLocationKey, null);
  3677.   },
  3678.   
  3679.   /**
  3680.    * Installs an item from a XPI/JAR file.
  3681.    * @param   aXPIFile
  3682.    *          The file to install from.
  3683.    * @param   aInstallLocationKey
  3684.    *          The name of the Install Location where this item should be 
  3685.    *          installed.
  3686.    * @param   aInstallManifest
  3687.    *          An updated Install Manifest from the Version Update check.
  3688.    *          Can be null when invoked from callers other than the Version
  3689.    *          Update check.
  3690.    */
  3691.   installItemFromFileInternal: function(aXPIFile, aInstallLocationKey, aInstallManifest) {
  3692.     var em = this;
  3693.     /**
  3694.      * Gets the Install Location for an Item.
  3695.      * @param   itemID 
  3696.      *          The GUID of the item to find an Install Location for.
  3697.      * @return  An object implementing nsIInstallLocation which represents the 
  3698.      *          location where the specified item should be installed. 
  3699.      *          This can be:
  3700.      *          1. an object that corresponds to the location key supplied to
  3701.      *             |installItemFromFileInternal|,
  3702.      *          2. the default install location (the App Profile Extensions Folder)
  3703.      *             if no location key was supplied, or the location key supplied
  3704.      *             was not in the set of registered locations
  3705.      *          3. null, if the location selected by 1 or 2 above does not support
  3706.      *             installs from XPI/JAR files, or that location is not writable 
  3707.      *             with the current access privileges.
  3708.      */
  3709.     function getInstallLocation(itemID) {
  3710.       // Here I use "upgrade" to mean "install a different version of an item".
  3711.       var installLocation = em.getInstallLocation(itemID);
  3712.       if (!installLocation) {
  3713.         // This is not an "upgrade", since we don't have any location data for the
  3714.         // extension ID specified - that is, it's not in our database.
  3715.  
  3716.         // Caller supplied a key to a registered location, use that location
  3717.         // for the installation
  3718.         installLocation = InstallLocations.get(aInstallLocationKey);
  3719.         if (installLocation) {
  3720.           // If the specified location does not have a common metadata location
  3721.           // (e.g. extensions have no common root, or other location specified
  3722.           // by the location implementation) - e.g. for a Registry Key enumeration
  3723.           // location - we cannot install or upgrade using a XPI file, probably
  3724.           // because these application types will be handling upgrading themselves.
  3725.           // Just bail.
  3726.           if (!installLocation.location) {
  3727.             LOG("Install Location \"" + installLocation.name + "\" does not support " + 
  3728.                 "installation of items from XPI/JAR files. You must manage " + 
  3729.                 "installation and update of these items yourself.");
  3730.             installLocation = null;
  3731.           }
  3732.         }
  3733.         else {
  3734.           // In the absence of a preferred install location, just default to
  3735.           // the App-Profile 
  3736.           installLocation = InstallLocations.get(KEY_APP_PROFILE);
  3737.         }
  3738.       } 
  3739.       else {
  3740.         // This is an "upgrade", but not through the Update System, because the
  3741.         // Update code will not let an extension with an incompatible target
  3742.         // app version range through to this point. This is an "upgrade" in the
  3743.         // sense that the user found a different version of an installed extension
  3744.         // and installed it through the web interface, so we have metadata.
  3745.         
  3746.         // If the location is different, return the preferred location rather than
  3747.         // the location of the currently installed version, because we may be in
  3748.         // the situation where an item is being installed into the global app 
  3749.         // dir when there's a version in the profile dir.
  3750.         if (installLocation.name != aInstallLocationKey) 
  3751.           installLocation = InstallLocations.get(aInstallLocationKey);
  3752.       }
  3753.       if (!installLocation.canAccess) {
  3754.         LOG("Install Location\"" + installLocation.name + "\" cannot be written " +
  3755.             "to with your access privileges. Installation will not proceed.");
  3756.         installLocation = null;
  3757.       }
  3758.       return installLocation;
  3759.     }
  3760.     
  3761.     /**
  3762.      * Stages a XPI file in the default item location specified by other 
  3763.      * applications when they registered with XulRunner if the item's
  3764.      * install manifest specified compatibility with them.
  3765.      */
  3766.     function stageXPIForOtherApps(xpiFile, installData) {
  3767.       for (var i = 0; i < installData.targetApps.length; ++i) {
  3768.         var targetApp = installData.targetApps[i];
  3769.         if (targetApp.id != gApp.ID) {
  3770.         /* XXXben uncomment when this works!
  3771.           var settingsThingy = Components.classes[]
  3772.                                         .getService(Components.interfaces.nsIXULRunnerSettingsThingy);
  3773.           try {
  3774.             var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\";
  3775.             var branch = settingsThingy.getBranch(appPrefix + targetApp.id);
  3776.             var path = branch.getProperty("ExtensionsLocation");
  3777.             var destination = Components.classes["@mozilla.org/file/local;1"]
  3778.                                         .createInstance(nsILocalFile);
  3779.             destination.initWithPath(path);
  3780.             xpiFile.copyTo(file, xpiFile.leafName);
  3781.           }
  3782.           catch (e) {
  3783.           }
  3784.          */
  3785.         } 
  3786.       }        
  3787.     }
  3788.     
  3789.     /**
  3790.      * Extracts and then starts the install for extensions / themes contained
  3791.      * within a xpi.
  3792.      */
  3793.     function installMultiXPI(xpiFile, installData) {
  3794.       var fileURL = getURIFromFile(xpiFile).QueryInterface(nsIURL);
  3795.       if (fileURL.fileExtension.toLowerCase() != "xpi") {
  3796.         LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " + 
  3797.             "invalid file extension. Only xpi file extensions are allowed for " +
  3798.             "multiple item packages.");
  3799.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3800.         showMessage("invalidFileExtTitle", [], 
  3801.                     "invalidFileExtMessage", [installData.name,
  3802.                     fileURL.fileExtension,
  3803.                     bundle.GetStringFromName("type-" + installData.type)]);
  3804.         return;
  3805.       }
  3806.  
  3807.       try {
  3808.         var zipReader = getZipReaderForFile(xpiFile);
  3809.       }
  3810.       catch (e) {
  3811.         LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path);
  3812.         throw e;
  3813.       }
  3814.  
  3815.       var searchForEntries = ["*.xpi", "*.jar"];
  3816.       var files = [];
  3817.       for (var i = 0; i < searchForEntries.length; ++i) {
  3818.         var entries = zipReader.findEntries(searchForEntries[i]);
  3819.         while (entries.hasMoreElements()) {
  3820.           var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  3821.           var target = getFile(KEY_TEMPDIR, [entry.name]);
  3822.           try {
  3823.             target.createUnique(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3824.           }
  3825.           catch (e) {
  3826.             LOG("installMultiXPI: failed to create target file for extraction " +
  3827.                 " file = " + target.path + ", exception = " + e + "\n");
  3828.           }
  3829.           zipReader.extract(entry.name, target);
  3830.           files.push(target);
  3831.         }
  3832.       }
  3833.       zipReader.close();
  3834.  
  3835.       if (files.length == 0) {
  3836.         LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " +
  3837.             "not contain a valid package to install.");
  3838.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3839.         showMessage("missingPackageFilesTitle",
  3840.                     [bundle.GetStringFromName("type-" + installData.type)],
  3841.                     "missingPackageFilesMessage", [installData.name,
  3842.                     bundle.GetStringFromName("type-" + installData.type)]);
  3843.         return;
  3844.       }
  3845.  
  3846.       // When installing themes we need to open the theme manager if it is not
  3847.       // already opened except when we are installing a xpi dropped into a
  3848.       // extensions directory.
  3849.       fileURL = getURIFromFile(files[files.length - 1]).QueryInterface(nsIURL);
  3850.       if (fileURL.fileExtension.toLowerCase() == "jar") {
  3851.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3852.                            .getService(Components.interfaces.nsIWindowMediator);
  3853.         var win = wm.getMostRecentWindow("Extension:Manager-extensions");
  3854.         if (win) {
  3855.           win = wm.getMostRecentWindow("Extension:Manager-themes");
  3856.           if (win) {
  3857.             win.focus();
  3858.           }
  3859.           else {
  3860.             var themeManagerURL = gPref.getCharPref(PREF_XPINSTALL_STATUS_DLG_SKIN);
  3861.             var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3862.                                .getService(Components.interfaces.nsIWindowWatcher);
  3863.             ww.openWindow(null, themeManagerURL, 
  3864.                           "", "chrome,centerscreen,titlebar,dialog=no,resizable", []);
  3865.           }
  3866.         }
  3867.       }
  3868.  
  3869.       for (i = 0; i < files.length; ++i) {
  3870.         em.installItemFromFileInternal(files[i], aInstallLocationKey, null);
  3871.         files[i].remove(false);
  3872.       }
  3873.     }
  3874.  
  3875.     /**
  3876.      * An observer for the Extension Update System.
  3877.      * @constructor
  3878.      */
  3879.     function IncompatibleObserver() {}
  3880.     IncompatibleObserver.prototype = {
  3881.       _id: null,
  3882.       _type: nsIUpdateItem.TYPE_ANY,
  3883.       _xpi: null,
  3884.       _installManifest: null,
  3885.       _installRDF: null,
  3886.       
  3887.       /** 
  3888.        * Ask the Extension Update System if there are any version updates for
  3889.        * this item that will allow it to be compatible with this version of 
  3890.        * the Application.
  3891.        * @param   installManifest 
  3892.        *          The Install Manifest datasource for the item.
  3893.        * @param   installData
  3894.        *          The Install Data object for the item.
  3895.        * @param   xpiFile         
  3896.        *          The staged source XPI file that contains the item. Cleaned 
  3897.        *          up by this process.
  3898.        */
  3899.       checkForUpdates: function(installManifest, installData, xpiFile, installRDF) {
  3900.         this._id              = installData.id;
  3901.         this._type            = installData.type;
  3902.         this._xpi             = xpiFile;
  3903.         this._installManifest = installManifest;
  3904.         this._installRDF      = installRDF;
  3905.         
  3906.         var item = makeItem(installData.id, installData.version, 
  3907.                             aInstallLocationKey, 
  3908.                             installData.currentApp.minVersion, 
  3909.                             installData.currentApp.maxVersion,
  3910.                             installData.name,
  3911.                             "", /* XPI Update URL */
  3912.                             "", /* XPI Update Hash */
  3913.                             "", /* Icon URL */
  3914.                             installData.updateURL || "", 
  3915.                             installData.type);
  3916.         em.update([item], 1, true, this);
  3917.       },
  3918.       
  3919.       /**
  3920.        * See nsIExtensionManager.idl
  3921.        */
  3922.       onUpdateStarted: function() {
  3923.         LOG("Phone Home Listener: Update Started");
  3924.         em.datasource.onUpdateStarted();
  3925.       },
  3926.       
  3927.       /**
  3928.        * See nsIExtensionManager.idl
  3929.        */
  3930.       onUpdateEnded: function() {
  3931.         LOG("Phone Home Listener: Update Ended");
  3932.         // We are responsible for cleaning up this file!
  3933.         this._installRDF.remove(false);
  3934.         em.datasource.onUpdateEnded();
  3935.       },
  3936.       
  3937.       /**
  3938.        * See nsIExtensionManager.idl
  3939.        */
  3940.       onAddonUpdateStarted: function(addon) {
  3941.         LOG("Phone Home Listener: Update For " + addon.id + " started");
  3942.         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
  3943.                                                 addon.type, addon.version);
  3944.         em.datasource.onAddonUpdateStarted(addon);
  3945.       },
  3946.       
  3947.       /**
  3948.        * See nsIExtensionManager.idl
  3949.        */
  3950.       onAddonUpdateEnded: function(addon, status) {
  3951.         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status); 
  3952.         em.datasource.removeDownload(this._xpi.path);
  3953.         LOG("Version Check Phone Home Completed");
  3954.         // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently
  3955.         // supported
  3956.         if (status == nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) {
  3957.           em.datasource.setTargetApplicationInfo(addon.id, 
  3958.                                                  addon.minAppVersion,
  3959.                                                  addon.maxAppVersion, 
  3960.                                                  this._installManifest);
  3961.  
  3962.           // Try and install again, but use the updated compatibility DB
  3963.           em.installItemFromFileInternal(this._xpi, aInstallLocationKey, 
  3964.                                          this._installManifest);
  3965.  
  3966.           // Add the updated compatibility info to the datasource if done
  3967.           if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) {
  3968.             em.datasource.updateTargetAppInfo(addon.id, addon.minAppVersion,
  3969.                                               addon.maxAppVersion);
  3970.           }
  3971.           else { // needs a restart
  3972.             // Add updatedMinVersion and updatedMaxVersion so it can be used
  3973.             // to update the data sources during the installation or upgrade.
  3974.             em.datasource.setUpdatedTargetAppInfo(addon.id, addon.minAppVersion,
  3975.                                                   addon.maxAppVersion);
  3976.           }
  3977.           // Prevent the datasource file from being lazily recreated after
  3978.           // it is deleted by calling Flush.
  3979.           this._installManifest.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  3980.           this._installManifest.Flush();
  3981.         }
  3982.         else {
  3983.           em.datasource.removeDownload(this._xpi.path);
  3984.           showIncompatibleError(installData);
  3985.           // We are responsible for cleaning up this file!
  3986.           InstallLocations.get(aInstallLocationKey).removeFile(this._xpi);
  3987.         }
  3988.         em.datasource.onAddonUpdateEnded(addon, status);
  3989.       },
  3990.  
  3991.       /**
  3992.        * See nsISupports.idl
  3993.        */
  3994.       QueryInterface: function(iid) {
  3995.         if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
  3996.             !iid.equals(Components.interfaces.nsISupports))
  3997.           throw Components.results.NS_ERROR_NO_INTERFACE;
  3998.         return this;
  3999.       }
  4000.     }
  4001.  
  4002.     var installManifestFile = extractRDFFileToTempDir(aXPIFile, FILE_INSTALL_MANIFEST, true);
  4003.     var shouldPhoneHomeIfNecessary = false;
  4004.     if (!aInstallManifest) {
  4005.       // If we were not called with an Install Manifest, we were called from 
  4006.       // some other path than the Phone Home system, so we do want to phone
  4007.       // home if the version is incompatible.
  4008.       shouldPhoneHomeIfNecessary = true;
  4009.       var installManifest = getInstallManifest(installManifestFile);
  4010.       if (!installManifest) {
  4011.         LOG("The Install Manifest supplied by this item is not well-formed. " + 
  4012.             "Installation will not proceed.");
  4013.         installManifestFile.remove(false);
  4014.         return;
  4015.       }
  4016.     }
  4017.     else
  4018.       installManifest = aInstallManifest;
  4019.     
  4020.     var installData = this._getInstallData(installManifest);
  4021.     switch (installData.error) {
  4022.     case INSTALLERROR_INCOMPATIBLE_VERSION:
  4023.       // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or
  4024.       // not we need it (we may need it if a remote version bump that makes it 
  4025.       // compatible is discovered by the call home) - so we must stage it for 
  4026.       // later ourselves.
  4027.       if (shouldPhoneHomeIfNecessary && installData.currentApp) {
  4028.         var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4029.         if (!installLocation) {
  4030.           installManifestFile.remove(false);
  4031.           return;
  4032.         }
  4033.         var stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4034.         (new IncompatibleObserver(this)).checkForUpdates(installManifest, 
  4035.                                                          installData, stagedFile,
  4036.                                                          installManifestFile);
  4037.         // Return early to prevent deletion of the install manifest file.
  4038.         return;
  4039.       }
  4040.       else {
  4041.         // XXXben Look up XULRunnerSettingsThingy to see if there is a registered
  4042.         //        app that can handle this item, if so just stage and don't show
  4043.         //        this error!
  4044.         showIncompatibleError(installData);
  4045.       }
  4046.       break;
  4047.     case INSTALLERROR_SUCCESS:
  4048.       // Installation of multiple extensions / themes contained within a single xpi.
  4049.       if (installData.type == nsIUpdateItem.TYPE_MULTI_XPI) {
  4050.         installMultiXPI(aXPIFile, installData);
  4051.         break;
  4052.       }
  4053.  
  4054.       // Stage the extension's XPI so it can be extracted at the next restart.
  4055.       var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4056.       if (!installLocation) {
  4057.         // No cleanup of any of the staged XPI files should be required here, 
  4058.         // because this should only ever fail on the first recurse through
  4059.         // this function, BEFORE staging takes place... technically speaking
  4060.         // a location could become readonly during the phone home process, 
  4061.         // but that's an edge case I don't care about.
  4062.         installManifestFile.remove(false);
  4063.         return;
  4064.       }
  4065.  
  4066.       // Stage a copy of the XPI/JAR file for our own evil purposes...
  4067.       stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4068.       
  4069.       var restartRequired = this.installRequiresRestart(installData.id, 
  4070.                                                         installData.type);
  4071.       // Determine which configuration function to use based on whether or not
  4072.       // there is data about this item in our datasource already - if there is 
  4073.       // we want to upgrade, otherwise we install fresh.
  4074.       var ds = this.datasource;
  4075.       if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) {
  4076.         // We enter this function if any data corresponding to an existing GUID
  4077.         // is found, regardless of its Install Location. We need to check before
  4078.         // "upgrading" an item that Install Location of the new item is of equal
  4079.         // or higher priority than the old item, to make sure the datasource only
  4080.         // ever tracks metadata for active items.
  4081.         var oldInstallLocation = this.getInstallLocation(installData.id);
  4082.         if (oldInstallLocation.priority >= installLocation.priority) {
  4083.           this._upgradeItem(installManifest, installData.id, installLocation, 
  4084.                             installData.type);
  4085.           if (!restartRequired) {
  4086.             this._finalizeUpgrade(installData.id);
  4087.             this._finalizeInstall(installData.id, stagedFile);
  4088.           }
  4089.         }
  4090.       }
  4091.       else {
  4092.         this._configureForthcomingItem(installManifest, installData.id, 
  4093.                                         installLocation, installData.type);
  4094.         if (!restartRequired)
  4095.           this._finalizeInstall(installData.id, stagedFile);
  4096.       }
  4097.       this._updateManifests(restartRequired);
  4098.       break;
  4099.     case INSTALLERROR_INVALID_GUID:
  4100.       LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" + 
  4101.           " which is not well-formed.");
  4102.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4103.       showMessage("incompatibleTitle", 
  4104.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4105.                   "invalidGUIDMessage", [installData.name, installData.id]);
  4106.       break;
  4107.     case INSTALLERROR_INVALID_VERSION:
  4108.       LOG("Invalid Version: Item: \"" + installData.id + "\" has version " + 
  4109.           installData.version + " which is not well-formed.");
  4110.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4111.       showMessage("incompatibleTitle", 
  4112.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4113.                   "invalidVersionMessage", [installData.name, installData.version]);
  4114.       break;
  4115.     case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  4116.       const osABI = gOSTarget + "_" + gXPCOMABI;
  4117.       LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " + 
  4118.           "compatible with '" + osABI + "'.");
  4119.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4120.       showMessage("incompatibleTitle", 
  4121.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4122.                   "incompatiblePlatformMessage",
  4123.                   [installData.name, BundleManager.appName, osABI]);
  4124.       break;
  4125.     default:
  4126.       break;
  4127.     }
  4128.     
  4129.     // Check to see if this item supports other applications and in that case
  4130.     // stage the the XPI file in the location specified by those applications.
  4131.     stageXPIForOtherApps(aXPIFile, installData);
  4132.  
  4133.     installManifestFile.remove(false);
  4134.   },
  4135.   
  4136.   /**
  4137.    * Whether or not this type's installation/uninstallation requires 
  4138.    * the application to be restarted.
  4139.    * @param   id
  4140.    *          The GUID of the item
  4141.    * @param   type
  4142.    *          The nsIUpdateItem type of the item
  4143.    * @returns true if installation of an item of this type requires a 
  4144.    *          restart.
  4145.    */
  4146.   installRequiresRestart: function(id, type) {
  4147.     switch (type) {
  4148.     case nsIUpdateItem.TYPE_EXTENSION:
  4149.       return true;
  4150.     case nsIUpdateItem.TYPE_THEME:
  4151.       var internalName = this.datasource.getItemProperty(id, "internalName");
  4152.       var needsRestart = false;
  4153.       if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
  4154.         needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  4155.       if (!needsRestart &&
  4156.           gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
  4157.         needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  4158.       return needsRestart;
  4159.     }
  4160.     return false;
  4161.   },
  4162.   
  4163.   /**
  4164.    * Perform initial configuration on an item that has just or will be 
  4165.    * installed. This inserts the item into the appropriate container in the
  4166.    * datasource, so that the application UI shows the item even if it will
  4167.    * not actually be installed until the next restart.
  4168.    * @param   installManifest 
  4169.    *          The Install Manifest datasource that describes this item.
  4170.    * @param   id          
  4171.    *          The GUID of this item.
  4172.    * @param   installLocation
  4173.    *          The Install Location where this item is installed.
  4174.    * @param   type
  4175.    *          The nsIUpdateItem type of this item. 
  4176.    */  
  4177.   _configureForthcomingItem: function(installManifest, id, installLocation, type) {
  4178.     var ds = this.datasource;
  4179.     ds.updateVisibleList(id, installLocation.name, false);
  4180.     var props = { name            : EM_L(getManifestProperty(installManifest, "name")),
  4181.                   version         : EM_L(getManifestProperty(installManifest, "version")),
  4182.                   installLocation : EM_L(installLocation.name),
  4183.                   type            : EM_I(type),
  4184.                   availableUpdateURL    : null,
  4185.                   availableUpdateHash   : null,
  4186.                   availableUpdateVersion: null };
  4187.     for (var p in props)
  4188.       ds.setItemProperty(id, EM_R(p), props[p]);
  4189.     ds.updateProperty(id, "displayDescription");
  4190.     ds.updateProperty(id, "availableUpdateURL");
  4191.     
  4192.     this._setOp(id, OP_NEEDS_INSTALL);
  4193.     
  4194.     // Insert it into the child list NOW rather than later because:
  4195.     // - extensions installed using the command line need to be a member
  4196.     //   of a container during the install phase for the code to be able
  4197.     //   to identify profile vs. global
  4198.     // - extensions installed through the UI should show some kind of
  4199.     //   feedback to indicate their presence is forthcoming (i.e. they
  4200.     //   will be available after a restart).
  4201.     ds.insertItemIntoContainer(id);
  4202.     
  4203.     this._notifyAction(id, EM_ITEM_INSTALLED);
  4204.   },
  4205.   
  4206.   /**
  4207.    * Perform configuration on an item that has just or will be upgraded.
  4208.    * @param   installManifest
  4209.    *          The Install Manifest datasource that describes this item.
  4210.    * @param   itemID
  4211.    *          The GUID of this item.
  4212.    * @param   installLocation
  4213.    *          The Install Location where this item is installed.
  4214.    * @param   type
  4215.    *          The nsIUpdateItem type of this item. 
  4216.    */
  4217.   _upgradeItem: function (installManifest, id, installLocation, type) {
  4218.     // Don't change any props that would need to be reset if the install fails.
  4219.     // They will be reset as appropriate by the upgrade/install process.
  4220.     var ds = this.datasource;
  4221.     ds.updateVisibleList(id, installLocation.name, false);
  4222.     var props = { installLocation : EM_L(installLocation.name),
  4223.                   type            : EM_I(type),
  4224.                   availableUpdateURL      : null,
  4225.                   availableUpdateHash     : null,
  4226.                   availableUpdateVersion  : null };
  4227.     for (var p in props)
  4228.       ds.setItemProperty(id, EM_R(p), props[p]);
  4229.     ds.updateProperty(id, "displayDescription");
  4230.     ds.updateProperty(id, "availableUpdateURL");
  4231.  
  4232.     this._setOp(id, OP_NEEDS_UPGRADE);
  4233.     this._notifyAction(id, EM_ITEM_UPGRADED);
  4234.   },
  4235.  
  4236.   /** 
  4237.    * Completes an Extension's installation.
  4238.    * @param   id
  4239.    *          The GUID of the Extension to install.
  4240.    * @param   file
  4241.    *          The XPI/JAR file to install from. If this is null, we try to
  4242.    *          determine the stage file location from the ID.
  4243.    */
  4244.   _finalizeInstall: function(id, file) {
  4245.     var ds = this.datasource;
  4246.     var type = ds.getItemProperty(id, "type");
  4247.     if (id == 0 || id == -1) {
  4248.       ds.removeCorruptItem(id, type);
  4249.       return;
  4250.     }
  4251.     var installLocation = this.getInstallLocation(id);
  4252.     if (!installLocation) {
  4253.       // If the install location is null, that means we've reached the finalize
  4254.       // state without the item ever having metadata added for it, which implies
  4255.       // bogus data in the Startup Cache. Clear the entries and don't do anything
  4256.       // else.
  4257.       var entries = StartupCache.findEntries(id);
  4258.       for (var i = 0; i < entries.length; ++i) {
  4259.         var location = InstallLocations.get(entries[i].location);
  4260.         StartupCache.clearEntry(location, id);
  4261.         PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4262.       }
  4263.       return;
  4264.     }
  4265.     var itemLocation = installLocation.getItemLocation(id);
  4266.  
  4267.     if (!file && "stageFile" in installLocation)
  4268.       file = installLocation.getStageFile(id);
  4269.     
  4270.     // If |file| is null or does not exist, the installer assumes the item is
  4271.     // a dropped-in directory.
  4272.     var installer = new Installer(this.datasource, id, installLocation, type);
  4273.     installer.installFromFile(file);
  4274.  
  4275.     // If the file was staged, we must clean it up ourselves, otherwise the 
  4276.     // EM caller is responsible for doing so (e.g. XPInstall)
  4277.     if (file)
  4278.       installLocation.removeFile(file);
  4279.     
  4280.     // Clear the op flag from the Startup Cache and Pending Operations sets
  4281.     StartupCache.put(installLocation, id, OP_NONE, true);
  4282.     PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4283.   },
  4284.  
  4285.   /**
  4286.    * Removes an item's metadata in preparation for an upgrade-install.
  4287.    * @param   id
  4288.    *          The GUID of the item to uninstall.
  4289.    */
  4290.   _finalizeUpgrade: function(id) {
  4291.     // Retrieve the item properties *BEFORE* we clean the resource!
  4292.     var ds = this.datasource;
  4293.     var installLocation = this.getInstallLocation(id);
  4294.  
  4295.     var stagedFile = null;
  4296.     if ("getStageFile" in installLocation)
  4297.       stagedFile = installLocation.getStageFile(id);
  4298.  
  4299.     if (stagedFile)
  4300.       var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true);
  4301.     else
  4302.       installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  4303.     if (installRDF.exists()) {
  4304.       var installManifest = getInstallManifest(installRDF);
  4305.       if (installManifest) {
  4306.         var type = getAddonTypeFromInstallManifest(installManifest);
  4307.         var userDisabled = ds.getItemProperty(id, "userDisabled") == "true";
  4308.  
  4309.         // Clean the item resource
  4310.         ds.removeItemMetadata(id);
  4311.         // Now set up the properties on the item to mimic an item in its
  4312.         // "initial state" for installation.
  4313.         this._configureForthcomingItem(installManifest, id, installLocation, 
  4314.                                        type);
  4315.         if (userDisabled)
  4316.           ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  4317.       }
  4318.       if (stagedFile)
  4319.         installRDF.remove(false);
  4320.     }
  4321.     // Clear the op flag from the Pending Operations set. Do NOT clear op flag in 
  4322.     // the startup cache since this may have been reset to OP_NEEDS_INSTALL by
  4323.     // |_configureForthcomingItem|.
  4324.     PendingOperations.clearItem(OP_NEEDS_UPGRADE, id);
  4325.   },
  4326.   
  4327.   /**
  4328.    * Completes an item's uninstallation.
  4329.    * @param   id
  4330.    *          The GUID of the item to uninstall.
  4331.    */
  4332.   _finalizeUninstall: function(id) {
  4333.     var ds = this.datasource;
  4334.     
  4335.     var installLocation = this.getInstallLocation(id);
  4336.     if (!installLocation.itemIsManagedIndependently(id)) {
  4337.       try {
  4338.         // Having a callback that does nothing just causes the directory to be
  4339.         // removed.
  4340.         safeInstallOperation(id, installLocation, 
  4341.                              { data: null, callback: function() { } });
  4342.       }
  4343.       catch (e) {
  4344.         LOG("_finalizeUninstall: failed to remove directory for item: " + id + 
  4345.             " at Install Location: " + installLocation.name + ", rolling back uninstall");
  4346.         // Removal of the files failed, reset the uninstalled flag and rewrite
  4347.         // the install manifests so this item's components are registered.
  4348.         // Clear the op flag from the Startup Cache
  4349.         StartupCache.put(installLocation, id, OP_NONE, true);
  4350.         var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4351.         this._updateManifests(restartRequired);
  4352.         return;
  4353.       }
  4354.     }
  4355.     else if (installLocation.name == KEY_APP_PROFILE ||
  4356.              installLocation.name == KEY_APP_GLOBAL) {
  4357.       // Check for a pointer file and remove it if it exists
  4358.       var pointerFile = installLocation.location.clone();
  4359.       pointerFile.append(id);
  4360.       if (pointerFile.exists() && !pointerFile.isDirectory())
  4361.         pointerFile.remove(false);
  4362.     }
  4363.     
  4364.     // Clean the item resource
  4365.     ds.removeItemMetadata(id);
  4366.     
  4367.     // Do this LAST since inferences are made about an item based on
  4368.     // what container it's in.
  4369.     ds.removeItemFromContainer(id);
  4370.     
  4371.     // Clear the op flag from the Startup Cache and the Pending Operations set.
  4372.     StartupCache.clearEntry(installLocation, id);
  4373.     PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id);
  4374.   },
  4375.   
  4376.   /**
  4377.    * Uninstalls an item. If the uninstallation cannot be performed immediately
  4378.    * it is scheduled for the next restart.
  4379.    * @param   id
  4380.    *          The GUID of the item to uninstall.
  4381.    */
  4382.   uninstallItem: function(id) {
  4383.     var em = this;
  4384.     /**
  4385.      * Cleans up any staged xpis for this item when we uninstall. This fixes 
  4386.      * this scenario:
  4387.      * 1 install Foo 1.0, and restart
  4388.      * 2 install Foo 1.1, 
  4389.      * 3 without restarting, uninstall Foo 1.1
  4390.      * 4 Foo is uninstalled but the staged XPI from the install operation in
  4391.      *   step 2 lingers, and is offered to the user on the next startup to
  4392.      *   be installed.
  4393.      * @param   id
  4394.      *          The GUID of the item to look for staged files for.
  4395.      */
  4396.     function cleanUpPendingStageFile(id) {
  4397.       var foundStageOp = false;
  4398.       var stageOps = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  4399.       stageOps = stageOps.concat(PendingOperations.getOperations(OP_NEEDS_INSTALL));
  4400.       for (var i = 0; i < stageOps.length; ++i) {
  4401.         if (stageOps[i].id == id) {
  4402.           foundStageOp = true;
  4403.           break;
  4404.         }
  4405.       }
  4406.       
  4407.       if (foundStageOp) {
  4408.         var location = em.getInstallLocation(id);
  4409.         var stageFile = location.getStageFile(id);
  4410.         if (stageFile)
  4411.           location.removeFile(stageFile);
  4412.       }
  4413.     }
  4414.   
  4415.     var ds = this.datasource;
  4416.     var type = ds.getItemProperty(id, "type");
  4417.     if (!ds.isDownloadItem(id)) {
  4418.       cleanUpPendingStageFile(id);
  4419.       this._setOp(id, OP_NEEDS_UNINSTALL);
  4420.       var restartRequired = this.installRequiresRestart(id, type);
  4421.       if (!restartRequired) {
  4422.         this._finalizeUninstall(id);
  4423.         this._updateManifests(restartRequired);
  4424.       }
  4425.     }
  4426.     else {
  4427.       // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi"
  4428.       // ... just remove it from the list. 
  4429.       ds.removeCorruptDLItem(id);
  4430.     }
  4431.     
  4432.     this._notifyAction(id, EM_ITEM_UNINSTALLED);
  4433.   },
  4434.  
  4435.   /**
  4436.    * Sets the pending operation for a visible item. 
  4437.    * @param   id
  4438.    *          The GUID of the item
  4439.    * @param   op
  4440.    *          The name of the operation to be performed
  4441.    */  
  4442.   _setOp: function(id, op) {
  4443.     var location = this.getInstallLocation(id);
  4444.     StartupCache.put(location, id, op, true);
  4445.     PendingOperations.addItem(op, { locationKey: location.name, id: id });
  4446.     var ds = this.datasource;
  4447.     ds.updateProperty(id, "opType");
  4448.     ds.updateProperty(id, "updateable");
  4449.     ds.updateProperty(id, "displayDescription");
  4450.     var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4451.     this._updateManifests(restartRequired);
  4452.   },
  4453.   
  4454.   /**
  4455.    * Enables an item for the application (e.g. the item meets all requirements
  4456.    * for it to be enabled). If the item is not disabled by the user this will
  4457.    * also set the needs-enable operation for the next restart.
  4458.    * @param   id
  4459.    *          The ID of the item to be enabled by the application.
  4460.    */
  4461.   _appEnableItem: function(id) {
  4462.     var ds = this.datasource;
  4463.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4464.       this._setOp(id, OP_NEEDS_ENABLE);
  4465.       this._notifyAction(id, EM_ITEM_ENABLED);
  4466.     }
  4467.     else {
  4468.       ds.setItemProperty(id, EM_R("appDisabled"), null);
  4469.       ds.updateProperty(id, "compatible");
  4470.       ds.updateProperty(id, "displayDescription");
  4471.     }
  4472.   },
  4473.  
  4474.   /**
  4475.    * Disables an item for the application (e.g. the item does not meets all
  4476.    * requirements like app compatibility for it to be enabled). If the item is
  4477.    * not disabled by the user this will also set the needs-disable operation
  4478.    * for the next restart.
  4479.    * @param   id
  4480.    *          The ID of the item to be disabled by the application.
  4481.    */
  4482.   _appDisableItem: function(id) {
  4483.     var ds = this.datasource;
  4484.     ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  4485.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4486.       this._setOp(id, OP_NEEDS_DISABLE);
  4487.       this._notifyAction(id, EM_ITEM_DISABLED);
  4488.     }
  4489.   },
  4490.     
  4491.   /**
  4492.    * Sets an item to be enabled by the user. If the item is already enabled this
  4493.    * clears the needs-enable operation for the next restart. If the item's
  4494.    * operation is set to needs-uninstall this will cancel the uninstall and set
  4495.    * the needs-enable operation for the next restart if the item is disabled.
  4496.    * 
  4497.    * @param   id
  4498.    *          The ID of the item to be enabled by the user.
  4499.    */
  4500.   enableItem: function(id) {
  4501.     var ds = this.datasource;
  4502.     if (ds.getItemProperty(id, "userDisabled") == "true" ||
  4503.         ds.getItemProperty(id, "appDisabled") == "true" &&
  4504.         ds.getItemProperty(id, "compatible") == "true") {
  4505.       this._setOp(id, OP_NEEDS_ENABLE);
  4506.       this._notifyAction(id, EM_ITEM_ENABLED);
  4507.     }
  4508.     else {
  4509.       this._setOp(id, OP_NONE);
  4510.       this._notifyAction(id, EM_ITEM_CANCEL);
  4511.     }
  4512.   },
  4513.   
  4514.   /**
  4515.    * Sets an item to be disabled by the user. If the item is already disabled
  4516.    * this clears the needs-disable operation for the next restart.
  4517.    * @param   id
  4518.    *          The ID of the item to be disabled by the user.
  4519.    */
  4520.   disableItem: function(id) {
  4521.     var ds = this.datasource;
  4522.     if (ds.getItemProperty(id, "userDisabled") == "true") {
  4523.       this._setOp(id, OP_NONE);
  4524.       this._notifyAction(id, EM_ITEM_CANCEL);
  4525.     }
  4526.     else {
  4527.       this._setOp(id, OP_NEEDS_DISABLE);
  4528.       this._notifyAction(id, EM_ITEM_DISABLED);
  4529.     }
  4530.   },
  4531.   
  4532.   /**
  4533.    * Notify observers of a change to an item that has been requested by the
  4534.    * user. 
  4535.    */
  4536.   _notifyAction: function(id, reason) {
  4537.     gOS.notifyObservers(this.datasource.getItemForID(id), 
  4538.                         EM_ACTION_REQUESTED_TOPIC, reason);
  4539.   },
  4540.   
  4541.   /**
  4542.    * See nsIExtensionManager.idl
  4543.    */
  4544.   update: function(items, itemCount, versionUpdateOnly, listener) {
  4545.     var appID = gApp.ID;
  4546.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  4547.                              gApp.version);
  4548.  
  4549.     if (items.length == 0)
  4550.       items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  4551.  
  4552.     var updater = new ExtensionItemUpdater(appID, appVersion, this);
  4553.     updater.checkForUpdates(items, items.length, versionUpdateOnly, listener);
  4554.   },
  4555.  
  4556.   /**
  4557.    * @returns An enumeration of all registered Install Locations.
  4558.    */
  4559.   get installLocations () {
  4560.     return InstallLocations.enumeration;
  4561.   },
  4562.   
  4563.   /**
  4564.    * Gets the Install Location where a visible Item is stored.
  4565.    * @param   id
  4566.    *          The GUID of the item to locate an Install Location for.
  4567.    * @returns The Install Location object where the item is stored.
  4568.    */
  4569.   getInstallLocation: function(id) {
  4570.     var key = this.datasource.visibleItems[id];
  4571.     return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null;
  4572.   },
  4573.   
  4574.   /**
  4575.    * Gets a nsIUpdateItem for the item with the specified id.
  4576.    * @param   id
  4577.    *          The GUID of the item to construct a nsIUpdateItem for.
  4578.    * @returns The nsIUpdateItem representing the item.
  4579.    */
  4580.   getItemForID: function(id) {
  4581.     return this.datasource.getItemForID(id);
  4582.   },
  4583.   
  4584.   /**
  4585.    * Retrieves a list of nsIUpdateItems of items matching the specified type.
  4586.    * @param   type
  4587.    *          The type of item to return.
  4588.    * @param   countRef
  4589.    *          The XPCJS reference to the number of items returned.
  4590.    * @returns An array of nsIUpdateItems matching the id/type filter.
  4591.    */
  4592.   getItemList: function(type, countRef) {
  4593.     return this.datasource.getItemList(type, countRef);
  4594.   },
  4595.  
  4596.   /**  
  4597.    * See nsIExtensionManager.idl
  4598.    */
  4599.   getIncompatibleItemList: function(id, version, type, includeDisabled, 
  4600.                                     countRef) {
  4601.     var items = this.datasource.getIncompatibleItemList(id, version ? version : undefined,
  4602.                                                         type, includeDisabled);
  4603.     countRef.value = items.length;
  4604.     return items;
  4605.   },
  4606.   
  4607.   /**
  4608.    * Move an Item to the index of another item in its container.
  4609.    * @param   movingID
  4610.    *          The ID of the item to be moved.
  4611.    * @param   destinationID
  4612.    *          The ID of an item to move another item to.
  4613.    */
  4614.   moveToIndexOf: function(movingID, destinationID) {
  4615.     this.datasource.moveToIndexOf(movingID, destinationID);
  4616.   },
  4617.  
  4618.   /////////////////////////////////////////////////////////////////////////////    
  4619.   // Downloads
  4620.   _transactions: [],
  4621.   _downloadCount: 0,
  4622.   
  4623.   /**
  4624.    * Ask the user if they really want to quit the application, since this will 
  4625.    * cancel one or more Extension/Theme downloads.
  4626.    * @param   subject
  4627.    *          A nsISupportsPRBool which this function sets to false if the user
  4628.    *          wishes to cancel all active downloads and quit the application,
  4629.    *          false otherwise.
  4630.    */
  4631.   _confirmCancelDownloadsOnQuit: function(subject) {
  4632.     if (this._downloadCount > 0) {
  4633.       var result;
  4634. //@line 4639 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4635.       result = this._confirmCancelDownloads(this._downloadCount, 
  4636.                                             "quitCancelDownloadsAlertTitle",
  4637.                                             "quitCancelDownloadsAlertMsgMultiple",
  4638.                                             "quitCancelDownloadsAlertMsg",
  4639.                                             "dontQuitButtonWin");
  4640. //@line 4651 "/c/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4641.       if (!result)
  4642.         this._cancelDownloads();
  4643.       if (subject instanceof Components.interfaces.nsISupportsPRBool)
  4644.         subject.data = result;
  4645.     }
  4646.   },
  4647.   
  4648.   /**
  4649.    * Ask the user if they really want to go offline, since this will cancel 
  4650.    * one or more Extension/Theme downloads.
  4651.    * @param   subject
  4652.    *          A nsISupportsPRBool which this function sets to false if the user
  4653.    *          wishes to cancel all active downloads and go offline, false
  4654.    *          otherwise.
  4655.    */
  4656.   _confirmCancelDownloadsOnOffline: function(subject) {
  4657.     if (this._downloadCount > 0) {
  4658.       result = this._confirmCancelDownloads(this._downloadCount,
  4659.                                             "offlineCancelDownloadsAlertTitle",
  4660.                                             "offlineCancelDownloadsAlertMsgMultiple",
  4661.                                             "offlineCancelDownloadsAlertMsg",
  4662.                                             "dontGoOfflineButton");
  4663.       if (!result)
  4664.         this._cancelDownloads();
  4665.       var PRBool = subject.QueryInterface(Components.interfaces.nsISupportsPRBool);
  4666.       PRBool.data = result;
  4667.     }
  4668.   },
  4669.   
  4670.   /**
  4671.    * Cancels all active downloads and removes them from the applicable UI.
  4672.    */
  4673.   _cancelDownloads: function() {
  4674.     for (var i = 0; i < this._transactions.length; ++i)
  4675.       gOS.notifyObservers(this._transactions[i], "xpinstall-progress", "cancel");
  4676.     gOS.removeObserver(this, "offline-requested");
  4677.     gOS.removeObserver(this, "quit-application-requested");
  4678.  
  4679.     this._removeAllDownloads();
  4680.   },
  4681.  
  4682.   /**
  4683.    * Ask the user whether or not they wish to cancel the Extension/Theme
  4684.    * downloads which are currently under way.
  4685.    * @param   count
  4686.    *          The number of active downloads.
  4687.    * @param   title
  4688.    *          The key of the title for the message box to be displayed
  4689.    * @param   cancelMessageMultiple
  4690.    *          The key of the message to be displayed in the message box
  4691.    *          when there are > 1 active downloads.
  4692.    * @param   cancelMessageSingle
  4693.    *          The key of the message to be displayed in the message box
  4694.    *          when there is just one active download.
  4695.    * @param   dontCancelButton
  4696.    *          The key of the label to be displayed on the "Don't Cancel 
  4697.    *          Downloads" button.
  4698.    */
  4699.   _confirmCancelDownloads: function(count, title, cancelMessageMultiple, 
  4700.                                     cancelMessageSingle, dontCancelButton) {
  4701.     var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES);
  4702.     var title = bundle.GetStringFromName(title);
  4703.     var message, quitButton;
  4704.     if (count > 1) {
  4705.       message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1);
  4706.       quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1);
  4707.     }
  4708.     else {
  4709.       message = bundle.GetStringFromName(cancelMessageSingle);
  4710.       quitButton = bundle.GetStringFromName("cancelDownloadsOKText");
  4711.     }
  4712.     var dontQuitButton = bundle.GetStringFromName(dontCancelButton);
  4713.     
  4714.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  4715.                        .getService(Components.interfaces.nsIWindowMediator);
  4716.     var win = wm.getMostRecentWindow("Extension:Manager");
  4717.     const nsIPromptService = Components.interfaces.nsIPromptService;
  4718.     var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  4719.                        .getService(nsIPromptService);
  4720.     var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) +
  4721.                 (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1);
  4722.     var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { });
  4723.     return rv == 1;
  4724.   },
  4725.   
  4726.   /** 
  4727.    * Adds a set of Item Downloads to the Manager and starts the download
  4728.    * operation.
  4729.    * @param   items
  4730.    *          An array of nsIUpdateItems to begin downlading.
  4731.    * @param   itemCount
  4732.    *          The length of |items|
  4733.    * @param   fromChrome
  4734.    *          true when called from chrome
  4735.    *          false when not called from chrome (e.g. web page)
  4736.    */
  4737.   addDownloads: function(items, itemCount, fromChrome) { 
  4738.     this._downloadCount += itemCount;
  4739.     
  4740.     var urls = [];
  4741.     var hashes = [];
  4742.     var txn = new ItemDownloadTransaction(this);
  4743.     for (var i = 0; i < itemCount; ++i) {
  4744.       var currItem = items[i];
  4745.       var txnID = Math.round(Math.random() * 100);
  4746.       txn.addDownload(currItem, txnID);
  4747.       this._transactions.push(txn);
  4748.       urls.push(currItem.xpiURL);
  4749.       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
  4750.     }
  4751.  
  4752.     // Kick off the download process for this transaction
  4753.     gOS.addObserver(this, "offline-requested", false);
  4754.     gOS.addObserver(this, "quit-application-requested", false);
  4755.     
  4756.     if (fromChrome) {
  4757.       // Initiate an install from chrome
  4758.       var xpimgr = 
  4759.           Components.classes["@mozilla.org/xpinstall/install-manager;1"].
  4760.           createInstance(Components.interfaces.nsIXPInstallManager);
  4761.       xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn);
  4762.     }
  4763.     else
  4764.       gOS.notifyObservers(txn, "xpinstall-progress", "open");
  4765.   },
  4766.   
  4767.   /**
  4768.    * Removes a download of a URL.
  4769.    * @param   url
  4770.    *          The URL of the item being downloaded to remove.
  4771.    */
  4772.   removeDownload: function(url) {
  4773.     for (var i = 0; i < this._transactions.length; ++i) {
  4774.       if (this._transactions[i].containsURL(url)) {
  4775.         this._transactions[i].removeDownload(url);
  4776.         return;
  4777.       }
  4778.     } 
  4779.   },
  4780.   
  4781.   /**
  4782.    * Remove all downloads from all transactions.
  4783.    */
  4784.   _removeAllDownloads: function() {
  4785.     for (var i = 0; i < this._transactions.length; ++i)
  4786.       this._transactions[i].removeAllDownloads();
  4787.   },
  4788.  
  4789.   /**
  4790.    * Download Operation State has changed from one to another. 
  4791.    * 
  4792.    * The nsIXPIProgressDialog implementation in the download transaction object
  4793.    * forwards notifications through these methods which we then pass on to any
  4794.    * front end objects implementing nsIExtensionDownloadListener that 
  4795.    * are listening. We maintain the master state of download operations HERE, 
  4796.    * not in the front end, because if the user closes the extension or theme 
  4797.    * managers during the downloads we need to maintain state and not terminate
  4798.    * the download/install process. 
  4799.    *
  4800.    * @param   transaction
  4801.    *          The ItemDownloadTransaction object receiving the download 
  4802.    *          notifications from XPInstall.
  4803.    * @param   addon
  4804.    *          An object representing nsIUpdateItem for the addon being updated
  4805.    * @param   state
  4806.    *          The state we are entering
  4807.    * @param   value
  4808.    *          ???
  4809.    */
  4810.   onStateChange: function(transaction, addon, state, value) {
  4811.     var url = addon.xpiURL;
  4812.     if (!(url in this._progressData)) 
  4813.       this._progressData[url] = { };
  4814.     this._progressData[url].state = state;
  4815.     
  4816.     for (var i = 0; i < this._updateListeners.length; ++i)
  4817.       this._updateListeners[i].onStateChange(addon, state, value);
  4818.  
  4819.     const nsIXPIProgressDialog = Components.interfaces.nsIXPIProgressDialog;
  4820.     switch (state) {
  4821.     case nsIXPIProgressDialog.INSTALL_DONE:
  4822.       --this._downloadCount;
  4823.       break;
  4824.     case nsIXPIProgressDialog.DIALOG_CLOSE:
  4825.       for (var i = 0; i < this._transactions.length; ++i) {
  4826.         if (this._transactions[i].id == transaction.id) {
  4827.           this._transactions.splice(i, 1);
  4828.           delete transaction;
  4829.           break;
  4830.         }
  4831.       }
  4832.       break;
  4833.     }
  4834.     // If we're updating an installed item for which content is already built,
  4835.     // update the "displayDescription" property so the restart now message is
  4836.     // shown.
  4837.     if (addon.id != addon.xpiURL) {
  4838.       var ds = this.datasource;
  4839.       ds.updateProperty(addon.id, "displayDescription");
  4840.       ds.updateProperty(addon.id, "availableUpdateURL");
  4841.       ds.updateProperty(addon.id, "updateable"); 
  4842.     }
  4843.   },
  4844.   
  4845.   _progressData: { },
  4846.   onProgress: function(addon, value, maxValue) {
  4847.     for (var i = 0; i < this._updateListeners.length; ++i)
  4848.       this._updateListeners[i].onProgress(addon, value, maxValue);
  4849.     
  4850.     var url = addon.xpiURL;
  4851.     if (!(url in this._progressData)) 
  4852.       this._progressData[url] = { };
  4853.     this._progressData[url].progress = Math.round((value / maxValue) * 100);
  4854.   },
  4855.  
  4856.   _updateListeners: [],
  4857.   addUpdateListener: function(listener) {
  4858.     for (var i = 0; i < this._updateListeners.length; ++i) {
  4859.       if (this._updateListeners[i] == listener)
  4860.         return i;
  4861.     }
  4862.     this._updateListeners.push(listener);
  4863.     return this._updateListeners.length - 1;
  4864.   },
  4865.   
  4866.   removeUpdateListenerAt: function(index) {
  4867.     this._updateListeners.splice(index, 1);
  4868.     if (this._downloadCount != 0)
  4869.       this.datasource.flushProgressInfo(this._progressData);
  4870.   },
  4871.  
  4872.   /**
  4873.    * The Extensions RDF Datasource
  4874.    */
  4875.   _ds: null,
  4876.  
  4877.   /** 
  4878.    * Loads the Extensions Datasource. This should not be called unless: 
  4879.    * - a piece of Extensions UI is being shown, or
  4880.    * - on startup and there has been a change to an Install Location
  4881.    * ... it should NOT be called on every startup!
  4882.    */
  4883.   _ensureDS: function() {
  4884.     if (!this._ds) {
  4885.       this._ds = new ExtensionsDataSource(this);
  4886.       if (this._ds)
  4887.         this._ds.loadExtensions();
  4888.     }
  4889.   },
  4890.  
  4891.   /**
  4892.    * See nsIExtensionManager.idl
  4893.    */
  4894.   get datasource() {
  4895.     this._ensureDS();
  4896.     return this._ds.QueryInterface(Components.interfaces.nsIRDFDataSource);
  4897.   },
  4898.   
  4899.   /**
  4900.    * See nsIClassInfo.idl
  4901.    */
  4902.   getInterfaces: function(count) {
  4903.     var interfaces = [Components.interfaces.nsIExtensionManager,
  4904.                       Components.interfaces.nsIXPIProgressDialog,
  4905.                       Components.interfaces.nsIObserver];
  4906.     count.value = interfaces.length;
  4907.     return interfaces;
  4908.   },
  4909.   getHelperForLanguage: function(language) { 
  4910.     return null;
  4911.   },
  4912.   get contractID() {
  4913.     return "@mozilla.org/extensions/manager;1";
  4914.   },
  4915.   get classDescription() {
  4916.     return "Extension Manager";
  4917.   },
  4918.   get classID() {
  4919.     return Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}");
  4920.   },
  4921.   get implementationLanguage() {
  4922.     return Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  4923.   },
  4924.   get flags() {
  4925.     return Components.interfaces.nsIClassInfo.SINGLETON;
  4926.   },
  4927.  
  4928.   /**
  4929.    * See nsISupports.idl
  4930.    */
  4931.   QueryInterface: function(iid) {
  4932.     if (!iid.equals(Components.interfaces.nsIExtensionManager) &&
  4933.         !iid.equals(Components.interfaces.nsITimerCallback) &&
  4934.         !iid.equals(Components.interfaces.nsIObserver) &&
  4935.         !iid.equals(Components.interfaces.nsISupports))
  4936.       throw Components.results.NS_ERROR_NO_INTERFACE;
  4937.     return this;
  4938.   }
  4939. };
  4940.  
  4941. /**
  4942.  * This object implements nsIXPIProgressDialog and represents a collection of
  4943.  * XPI/JAR download and install operations. There is one 
  4944.  * ItemDownloadTransaction per back-end XPInstallManager object. We maintain
  4945.  * a collection of separate transaction objects because it's possible to have
  4946.  * multiple separate XPInstall download/install operations going on 
  4947.  * simultaneously, each with its own XPInstallManager instance. For instance
  4948.  * you could start downloading two extensions and then download a theme. Each
  4949.  * of these operations would open the appropriate FE and have to be able to
  4950.  * track each operation independently.
  4951.  * 
  4952.  * @constructor
  4953.  */
  4954. function ItemDownloadTransaction(manager) {
  4955.   this._manager = manager;
  4956.   this._downloads = [];
  4957. }
  4958. ItemDownloadTransaction.prototype = {
  4959.   _manager    : null,
  4960.   _downloads  : [],
  4961.   id          : -1,
  4962.   
  4963.   /**
  4964.    * Add a download to this transaction
  4965.    * @param   addon
  4966.    *          An object implementing nsIUpdateItem for the item to be downloaded
  4967.    * @param   id
  4968.    *          The integer identifier of this transaction
  4969.    */
  4970.   addDownload: function(addon, id) {
  4971.     this._downloads.push({ addon: addon, waiting: true });
  4972.     this._manager.datasource.addDownload(addon);
  4973.     this.id = id;
  4974.   },
  4975.   
  4976.   /**
  4977.    * Removes a download from this transaction
  4978.    * @param   url
  4979.    *          The URL to remove
  4980.    */
  4981.   removeDownload: function(url) {
  4982.     this._manager.datasource.removeDownload(url);
  4983.   },
  4984.   
  4985.   /**
  4986.    * Remove all downloads from this transaction
  4987.    */
  4988.   removeAllDownloads: function() {
  4989.     for (var i = 0; i < this._downloads.length; ++i) {
  4990.       var addon = this._downloads[i].addon;
  4991.       this.removeDownload(addon.xpiURL, addon.type);
  4992.     }
  4993.   },
  4994.   
  4995.   /**
  4996.    * Determine if this transaction is handling the download of a url.
  4997.    * @param   url
  4998.    *          The URL to look for
  4999.    * @returns true if this transaction is downloading the supplied url.
  5000.    */
  5001.   containsURL: function(url) {
  5002.     for (var i = 0; i < this._downloads.length; ++i) {
  5003.       if (this._downloads[i].addon.xpiURL == url)
  5004.         return true;
  5005.     }
  5006.     return false;
  5007.   },
  5008.  
  5009.   /**
  5010.    * See nsIXPIProgressDialog.idl
  5011.    */
  5012.   onStateChange: function(index, state, value) {
  5013.     this._manager.onStateChange(this, this._downloads[index].addon, 
  5014.                                 state, value);
  5015.   },
  5016.   
  5017.   /**
  5018.    * See nsIXPIProgressDialog.idl
  5019.    */
  5020.   onProgress: function(index, value, maxValue) { 
  5021.     this._manager.onProgress(this._downloads[index].addon, value, maxValue);
  5022.   },
  5023.   
  5024.   /////////////////////////////////////////////////////////////////////////////
  5025.   // nsISupports
  5026.   QueryInterface: function(iid) {
  5027.     if (!iid.equals(Components.interfaces.nsIXPIProgressDialog) &&
  5028.         !iid.equals(Components.interfaces.nsISupports))
  5029.       throw Components.results.NS_ERROR_NO_INTERFACE;
  5030.     return this;
  5031.   }
  5032. };
  5033.  
  5034. /**
  5035.  * A listener object to the update check process that routes notifications to
  5036.  * the right places and keeps the datasource up to date.
  5037.  */
  5038. function AddonUpdateCheckListener(listener, datasource) {
  5039.   this._listener = listener;
  5040.   this._ds = datasource;
  5041. }
  5042. AddonUpdateCheckListener.prototype = {
  5043.   _listener: null,
  5044.   _ds: null,
  5045.   
  5046.   onUpdateStarted: function() {
  5047.     if (this._listener)
  5048.       this._listener.onUpdateStarted();
  5049.     this._ds.onUpdateStarted();
  5050.   },
  5051.   
  5052.   onUpdateEnded: function() {
  5053.     if (this._listener)
  5054.       this._listener.onUpdateEnded();
  5055.     this._ds.onUpdateEnded();
  5056.   },
  5057.   
  5058.   onAddonUpdateStarted: function(addon) {
  5059.     if (this._listener)
  5060.       this._listener.onAddonUpdateStarted(addon);
  5061.     this._ds.onAddonUpdateStarted(addon);
  5062.   },
  5063.   
  5064.   onAddonUpdateEnded: function(addon, status) {
  5065.     if (this._listener)
  5066.       this._listener.onAddonUpdateEnded(addon, status);
  5067.     this._ds.onAddonUpdateEnded(addon, status);
  5068.   }
  5069. };
  5070.  
  5071. ///////////////////////////////////////////////////////////////////////////////
  5072. //
  5073. // ExtensionItemUpdater
  5074. //
  5075. function ExtensionItemUpdater(aTargetAppID, aTargetAppVersion, aEM) 
  5076. {
  5077.   this._appID = aTargetAppID;
  5078.   this._appVersion = aTargetAppVersion;
  5079.   this._emDS = aEM._ds;
  5080.   this._em = aEM;
  5081.  
  5082.   getVersionChecker();
  5083. }
  5084.  
  5085. ExtensionItemUpdater.prototype = {
  5086.   _appID              : "",
  5087.   _appVersion         : "",
  5088.   _emDS               : null,
  5089.   _em                 : null,
  5090.   _versionUpdateOnly  : 0,
  5091.   _items              : [],
  5092.   _listener           : null,
  5093.   
  5094.   /////////////////////////////////////////////////////////////////////////////
  5095.   // ExtensionItemUpdater
  5096.   //
  5097.   // When we check for updates to an item, there are two pieces of information
  5098.   // that are returned - 1) info about the newest available version, if any,
  5099.   // and 2) info about the currently installed version. The latter is provided
  5100.   // primarily to inform the client of changes to the application compatibility 
  5101.   // metadata for the current item. Depending on the situation, either 2 or 
  5102.   // 1&2 may be what is required.
  5103.   //
  5104.   // Callers:
  5105.   //  1 - nsUpdateService.js, user event
  5106.   //      User clicked on the update icon to invoke an update check, 
  5107.   //      user clicked on an Extension/Theme and clicked "Update". In this
  5108.   //      case we want to update compatibility metadata about the installed
  5109.   //      version, and look for newer versions to offer. 
  5110.   //  2 - nsUpdateService.js, background event
  5111.   //      Timer fired, background update is being performed. In this case
  5112.   //      we also want to update compatibility metadata and look for newer
  5113.   //      versions.
  5114.   //  3 - Mismatch
  5115.   //      User upgraded to a newer version of the app, update compatibility
  5116.   //      metadata and look for newer versions.
  5117.   //  4 - Install Phone Home
  5118.   //      User installed an item that was deemed incompatible based only
  5119.   //      on the information provided in the item's install.rdf manifest, 
  5120.   //      we look ONLY for compatibility updates in this case to determine
  5121.   //      whether or not the item can be installed.
  5122.   //  
  5123.   checkForUpdates: function(aItems, aItemCount, aVersionUpdateOnly, 
  5124.                             aListener) {
  5125.     this._listener = new AddonUpdateCheckListener(aListener, this._emDS);
  5126.     if (this._listener)
  5127.       this._listener.onUpdateStarted();
  5128.     this._versionUpdateOnly = aVersionUpdateOnly;
  5129.     this._items = aItems;
  5130.     this._responseCount = aItemCount;
  5131.     
  5132.     // This is the number of extensions/themes/etc that we found updates for.
  5133.     this._updateCount = 0;
  5134.  
  5135.     for (var i = 0; i < aItemCount; ++i) {
  5136.       var e = this._items[i];
  5137.       if (this._listener)
  5138.         this._listener.onAddonUpdateStarted(e);
  5139.       (new RDFItemUpdater(this)).checkForUpdates(e, aVersionUpdateOnly);
  5140.     }
  5141.   },
  5142.   
  5143.   /////////////////////////////////////////////////////////////////////////////
  5144.   // ExtensionItemUpdater
  5145.   _applyVersionUpdates: function(aLocalItem, aRemoteItem) {
  5146.     var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS);
  5147.     // If targetAppInfo is null this is for a new install. If the local item's
  5148.     // maxVersion does not equal the targetAppInfo maxVersion then this is for
  5149.     // an upgrade. In both of these cases return true if the remotely specified
  5150.     // maxVersion is greater than the local item's maxVersion.
  5151.     if (!targetAppInfo ||
  5152.         gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) {
  5153.       if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0)
  5154.         return true;
  5155.       else
  5156.         return false;
  5157.     }
  5158.  
  5159.     if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) {
  5160.       // Remotely specified maxVersion is newer than the maxVersion 
  5161.       // for the installed Extension. Apply that change to the datasources.
  5162.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  5163.                                      aRemoteItem.maxAppVersion);
  5164.  
  5165.       // If we got here through |checkForMismatches|, this extension has
  5166.       // already been disabled, re-enable it.
  5167.       var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op;
  5168.       if (op == OP_NEEDS_DISABLE ||
  5169.           this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true")
  5170.         this._em._appEnableItem(aLocalItem.id);
  5171.       return true;
  5172.     }
  5173.     else if (this._versionUpdateOnly == 2)
  5174.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  5175.                                      aRemoteItem.maxAppVersion);
  5176.     return false;
  5177.   },
  5178.   
  5179.   _isValidUpdate: function(aLocalItem, aRemoteItem) {
  5180.     var appExtensionsVersion =
  5181.       getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION, gApp.version);
  5182.  
  5183.     // Check if the update will only run on a newer version of Firefox. 
  5184.     if (aRemoteItem.minAppVersion && 
  5185.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.minAppVersion) < 0) 
  5186.       return false;
  5187.  
  5188.     // Check if the update will only run on an older version of Firefox. 
  5189.     if (aRemoteItem.maxAppVersion && 
  5190.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.maxAppVersion) > 0) 
  5191.       return false;
  5192.     
  5193.     return true;
  5194.   },
  5195.   
  5196.   checkForDone: function(item, status) {
  5197.     if (this._listener) {
  5198.       try {
  5199.         this._listener.onAddonUpdateEnded(item, status);
  5200.       }
  5201.       catch (e) {
  5202.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e);
  5203.       }
  5204.     }
  5205.     if (--this._responseCount == 0 && this._listener) {
  5206.       try {
  5207.         this._listener.onUpdateEnded();
  5208.       }
  5209.       catch (e) {
  5210.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
  5211.       }
  5212.     }
  5213.   },
  5214. };
  5215.  
  5216. function RDFItemUpdater(aUpdater) {
  5217.   this._updater = aUpdater;
  5218. }
  5219.  
  5220. RDFItemUpdater.prototype = {
  5221.   _updater            : null,
  5222.   _versionUpdateOnly  : 0,
  5223.   _item               : null,
  5224.   
  5225.   checkForUpdates: function(aItem, aVersionUpdateOnly) {
  5226.     // A preference setting can disable updating for this item
  5227.     try {
  5228.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) {
  5229.         var status = nsIAddonUpdateCheckListener.STATUS_DISABLED;
  5230.         this._updater.checkForDone(aItem, status);
  5231.         return;
  5232.       }
  5233.     }
  5234.     catch (e) { }
  5235.  
  5236.     // Items managed by the app are not checked for updates.
  5237.     var emDS = this._updater._emDS;
  5238.     if (emDS.getItemProperty(aItem.id, "appManaged") == "true") {
  5239.       var status = nsIAddonUpdateCheckListener.STATUS_APP_MANAGED;
  5240.       this._updater.checkForDone(aItem, status);
  5241.       return;
  5242.     }
  5243.  
  5244.     // Items that have a pending install, uninstall, or upgrade are not checked
  5245.     // for updates.
  5246.     var opType = emDS.getItemProperty(aItem.id, "opType");
  5247.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  5248.         opType == OP_NEEDS_UPGRADE) {
  5249.       var status = nsIAddonUpdateCheckListener.STATUS_PENDING_OP;
  5250.       this._updater.checkForDone(aItem, status);
  5251.       return;
  5252.     }
  5253.  
  5254.     var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id));
  5255.     // Don't check items for updates that are installed in a location that is
  5256.     // not managed by the app.
  5257.     if (installLocation && (installLocation.name == "winreg-app-global" ||
  5258.         installLocation.name == "winreg-app-user")) {
  5259.       var status = nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED;
  5260.       this._updater.checkForDone(aItem, status);
  5261.       return;
  5262.     }
  5263.  
  5264.     // Don't check items for updates if the location can't be written to except
  5265.     // when performing a version only update.
  5266.     if (!aVersionUpdateOnly && (!installLocation || !installLocation.canAccess)) {
  5267.       var status = nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
  5268.       this._updater.checkForDone(aItem, status);
  5269.       return;
  5270.     }
  5271.  
  5272.     this._versionUpdateOnly = aVersionUpdateOnly;
  5273.     this._item = aItem;
  5274.   
  5275.     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
  5276.     // install manifest, 3) the default configuration
  5277.     try {
  5278.       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
  5279.                                         Components.interfaces.nsIPrefLocalizedString).data;
  5280.     }
  5281.     catch (e) { }
  5282.     if (!dsURI)
  5283.       dsURI = aItem.updateRDF;
  5284.     if (!dsURI) {
  5285.       dsURI = gPref.getComplexValue(PREF_UPDATE_DEFAULT_URL,
  5286.                                     Components.interfaces.nsIPrefLocalizedString).data;
  5287.     }
  5288.     dsURI = dsURI.replace(/%ITEM_ID%/g, aItem.id);
  5289.     dsURI = dsURI.replace(/%ITEM_VERSION%/g, aItem.version);
  5290.     dsURI = dsURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
  5291.     dsURI = dsURI.replace(/%APP_ID%/g, this._updater._appID);
  5292.     dsURI = dsURI.replace(/%APP_VERSION%/g, this._updater._appVersion);
  5293.     dsURI = dsURI.replace(/%REQ_VERSION%/g, 1);
  5294.     dsURI = dsURI.replace(/%APP_OS%/g, gOSTarget);
  5295.     dsURI = dsURI.replace(/%APP_ABI%/g, gXPCOMABI);
  5296.     
  5297.     // escape() does not properly encode + symbols in any embedded FVF strings.
  5298.     dsURI = dsURI.replace(/\+/g, "%2B");
  5299.  
  5300.     // Verify that the URI provided is valid
  5301.     try {
  5302.       var uri = newURI(dsURI);
  5303.     }
  5304.     catch (e) {
  5305.       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" + 
  5306.           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
  5307.       this._updater.checkForDone(aItem, 
  5308.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5309.       return;
  5310.     }
  5311.  
  5312.     LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " + 
  5313.         uri.spec + ", item = " + aItem.objectSource);        
  5314.  
  5315.     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  5316.                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  5317.     request.open("GET", uri.spec, true);
  5318.     request.overrideMimeType("text/xml");
  5319.     request.setRequestHeader("Cache-Control", "no-cache");
  5320.  
  5321.     var self = this;
  5322.     request.onerror     = function(event) { self.onXMLError(event, aItem);    };
  5323.     request.onload      = function(event) { self.onXMLLoad(event, aItem);     };
  5324.     request.send(null);
  5325.   },
  5326.  
  5327.   onXMLLoad: function(aEvent, aItem) {
  5328.     var request = aEvent.target;
  5329.     var responseXML = request.responseXML;
  5330.     if (responseXML)
  5331.       var parseError = (responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR);
  5332.  
  5333.     // If AMO does not return responseXML it is not treated as a failure since
  5334.     // items without an updateURL are checked on AMO. If there is an XML parse
  5335.     // error, responseXML is null, status does NOT equal 200 or 0 (e.g. 200 is
  5336.     // HTTP OK and 0 is returned for a local file) then we don't have valid data.
  5337.     if (!responseXML || parseError || (request.status != 200 && request.status != 0)) {
  5338.       // If the item does not have an updateRDF then the error is from UMO.
  5339.       if (!aItem.updateRDF) {
  5340.         this._updater.checkForDone(aItem, 
  5341.                                    nsIAddonUpdateCheckListener.STATUS_NONE);
  5342.       }
  5343.       else {
  5344.         this._updater.checkForDone(aItem, 
  5345.                                    nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5346.       }
  5347.  
  5348.       return;
  5349.     }
  5350.  
  5351.     var rdfParser = Components.classes["@mozilla.org/rdf/xml-parser;1"]
  5352.                               .createInstance(Components.interfaces.nsIRDFXMLParser)
  5353.     var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
  5354.                        .createInstance(Components.interfaces.nsIRDFDataSource);
  5355.     rdfParser.parseString(ds, request.channel.URI, request.responseText);
  5356.  
  5357.     this.onDatasourceLoaded(ds, aItem);
  5358.   },
  5359.  
  5360.   /**
  5361.    *
  5362.    */
  5363.   onXMLError: function(aEvent, aItem) {
  5364.     try {
  5365.       var request = aEvent.target;
  5366.       // the following may throw (e.g. a local file or timeout)
  5367.       var status = request.status;
  5368.     }
  5369.     catch (e) {
  5370.       request = aEvent.target.channel.QueryInterface(Components.interfaces.nsIRequest);
  5371.       status = request.status;
  5372.     }
  5373.  
  5374.     var statusText = request.statusText;
  5375.  
  5376.     // When status is 0 we don't have a valid channel.
  5377.     if (status == 0)
  5378.       statusText = "nsIXMLHttpRequest channel unavailable";
  5379.  
  5380.     LOG("RDFItemUpdater:onError: There was an error loading the \r\n" + 
  5381.         "the update datasource for item " + aItem.id + ", error: " + statusText);
  5382.     this._updater.checkForDone(aItem, 
  5383.                                nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5384.   },
  5385.  
  5386.   onDatasourceLoaded: function(aDatasource, aLocalItem) {
  5387.     ///////////////////////////////////////////////////////////////////////////    
  5388.     // The extension update RDF file looks something like this:
  5389.     //
  5390.     //  <RDF:Description about="urn:mozilla:extension:{GUID}">
  5391.     //    <em:updates>
  5392.     //      <RDF:Seq>
  5393.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:4.9"/>
  5394.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:5.0"/>
  5395.     //      </RDF:Seq>
  5396.     //    </em:updates>
  5397.     //    <!-- the version of the extension being offered -->
  5398.     //    <em:version>5.0</em:version>
  5399.     //    <em:updateLink>http://www.mysite.com/myext-50.xpi</em:updateLink>
  5400.     //  </RDF:Description>
  5401.     //
  5402.     //  <RDF:Description about="urn:mozilla:extension:{GUID}:4.9">
  5403.     //    <em:version>4.9</em:version>
  5404.     //    <em:targetApplication>
  5405.     //      <RDF:Description>
  5406.     //        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
  5407.     //        <em:minVersion>0.9</em:minVersion>
  5408.     //        <em:maxVersion>1.0</em:maxVersion>
  5409.     //        <em:updateLink>http://www.mysite.com/myext-49.xpi</em:updateLink>
  5410.     //      </RDF:Description>
  5411.     //    </em:targetApplication>
  5412.     //  </RDF:Description>  
  5413.     //
  5414.     // If we get here because the following happened:
  5415.     // 1) User was using Firefox 0.9 with ExtensionX 0.5 (minVersion 0.8, 
  5416.     //    maxVersion 0.9 for Firefox)
  5417.     // 2) User upgraded Firefox to 1.0
  5418.     // 3) |checkForMismatches| deems ExtensionX 0.5 incompatible with this
  5419.     //    new version of Firefox on the basis of its maxVersion
  5420.     // 4) ** We reach this point **
  5421.     //
  5422.     // If the version of ExtensionX (0.5) matches that provided by the 
  5423.     // server, then this is a cue that the author updated the rdf file
  5424.     // or central repository to say "0.5 is ALSO compatible with Firefox 1.0,
  5425.     // no changes are necessary." In this event, the local metadata for
  5426.     // installed ExtensionX (0.5) is freshened with the new maxVersion, 
  5427.     // and we advance to the next item WITHOUT any download/install 
  5428.     // updates.
  5429.     if (!aDatasource.GetAllResources().hasMoreElements()) {
  5430.       LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" + 
  5431.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5432.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5433.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5434.           "\r\nTry checking that: \r\n" + 
  5435.           " 1. Your remote RDF file exists at the location.\r\n" + 
  5436.           " 2. Your RDF file is valid XML (starts with <?xml version=\"1.0?\">\r\n" + 
  5437.           "    and loads in Firefox displaying pretty printed like other XML documents\r\n" + 
  5438.           " 3. Your server is sending the data in the correct MIME\r\n" + 
  5439.           "    type (text/xml)");
  5440.     }      
  5441.     
  5442.   
  5443.     // Parse the response RDF
  5444.     function UpdateData() {}; 
  5445.     UpdateData.prototype = { version: "0.0", updateLink: null, updateHash: null,
  5446.                              minVersion: "0.0", maxVersion: "0.0" };
  5447.     
  5448.     var versionUpdate = new UpdateData();
  5449.     var newestUpdate  = new UpdateData();
  5450.  
  5451.     var newerItem, sameItem;
  5452.     
  5453.     // Firefox 1.0PR+ update.rdf format
  5454.     if (!this._versionUpdateOnly) {
  5455.       // Look for newer versions of this item, we only do this in "normal" 
  5456.       // mode... see comment by ExtensionItemUpdater_checkForUpdates 
  5457.       // about how we do this in all cases but Install Phone Home - which 
  5458.       // only needs to do a version check.
  5459.       this._parseV20UpdateInfo(aDatasource, aLocalItem, newestUpdate, false);
  5460.       if (!newestUpdate.updateLink) {
  5461.         // Firefox 0.9 update.rdf format - does not contain any metadata
  5462.         // that can be used for version updates, so performed in the "all updates"
  5463.         // mode only. 
  5464.         this._parseV10UpdateInfo(aDatasource, aLocalItem, newestUpdate);
  5465.       }
  5466.  
  5467.       newerItem = makeItem(aLocalItem.id, 
  5468.                            newestUpdate.version, 
  5469.                            aLocalItem.installLocationKey,
  5470.                            newestUpdate.minVersion, 
  5471.                            newestUpdate.maxVersion, 
  5472.                            aLocalItem.name, 
  5473.                            newestUpdate.updateLink,
  5474.                            newestUpdate.updateHash,
  5475.                            "", /* Icon URL */
  5476.                            "", /* RDF Update URL */
  5477.                            aLocalItem.type);
  5478.       if (this._updater._isValidUpdate(aLocalItem, newerItem))
  5479.         ++this._updater._updateCount;
  5480.       else
  5481.         newerItem = null;
  5482.     }
  5483.     
  5484.     // Now look for updated version compatibility metadata for the currently
  5485.     // installed version...
  5486.     this._parseV20UpdateInfo(aDatasource, aLocalItem, versionUpdate, true);
  5487.  
  5488.     var result = gVersionChecker.compare(versionUpdate.version, 
  5489.                                           aLocalItem.version);
  5490.     if (result == 0) {
  5491.       // Local version exactly matches the "Version Update" remote version, 
  5492.       // Apply changes into local datasource.
  5493.       sameItem = makeItem(aLocalItem.id, 
  5494.                           versionUpdate.version, 
  5495.                           aLocalItem.installLocationKey,
  5496.                           versionUpdate.minVersion, 
  5497.                           versionUpdate.maxVersion, 
  5498.                           aLocalItem.name,
  5499.                           "", /* XPI Update URL */
  5500.                           "", /* XPI Update Hash */
  5501.                           "", /* Icon URL */
  5502.                           "", /* RDF Update URL */
  5503.                           aLocalItem.type);
  5504.       if (this._updater._isValidUpdate(aLocalItem, sameItem)) {
  5505.         // Install-time updates are not written to the DS because there is no
  5506.         // entry yet, EM just uses the notifications to ascertain (by hand)
  5507.         // whether or not there is a remote maxVersion tweak that makes the 
  5508.         // item being installed compatible.
  5509.         if (!this._updater._applyVersionUpdates(aLocalItem, sameItem))
  5510.           sameItem = null;
  5511.       }
  5512.       else 
  5513.         sameItem = null;
  5514.     }
  5515.     
  5516.     if (newerItem) {
  5517.       LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" + 
  5518.           newerItem.objectSource);
  5519.     }
  5520.     if (sameItem) {
  5521.       LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" + 
  5522.           "version of this item: " + sameItem.objectSource);
  5523.     }
  5524.     var item = null, status = nsIAddonUpdateCheckListener.STATUS_NONE;
  5525.     if (!this._versionUpdateOnly && newerItem) {
  5526.       item = newerItem;
  5527.       status = nsIAddonUpdateCheckListener.STATUS_UPDATE;
  5528.     }
  5529.     else if (sameItem) {
  5530.       item = sameItem;
  5531.       status = nsIAddonUpdateCheckListener.STATUS_VERSIONINFO;
  5532.     }
  5533.     else {
  5534.       item = aLocalItem;
  5535.       status = nsIAddonUpdateCheckListener.STATUS_NO_UPDATE;
  5536.     }
  5537.     // Only one call of this._updater.checkForDone is needed for RDF 
  5538.     // responses, since there is only one response per item.
  5539.     this._updater.checkForDone(item, status);
  5540.   },
  5541.  
  5542.   // Parses Firefox 0.9 update.rdf format  
  5543.   _parseV10UpdateInfo: function(aDataSource, aLocalItem, aUpdateData) {
  5544.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5545.     
  5546.     aUpdateData.version     = this._getPropertyFromResource(aDataSource, extensionRes, 
  5547.                                                             "version", aLocalItem);
  5548.     aUpdateData.updateLink  = this._getPropertyFromResource(aDataSource, extensionRes, 
  5549.                                                             "updateLink", aLocalItem);
  5550.   },
  5551.   
  5552.   // Get a compulsory property from a resource. Reports an error if the 
  5553.   // property was not present. 
  5554.   _getPropertyFromResource: function(aDataSource, aSourceResource, aProperty, aLocalItem) {
  5555.     var rv;
  5556.     try {
  5557.       var property = gRDF.GetResource(EM_NS(aProperty));
  5558.       rv = stringData(aDataSource.GetTarget(aSourceResource, property, true));
  5559.       if (rv === undefined)
  5560.         throw Components.results.NS_ERROR_FAILURE;
  5561.     }
  5562.     catch (e) {
  5563.       // XXXben show console message "aProperty" not found on aSourceResource. 
  5564.       return null;
  5565.     }
  5566.     return rv;
  5567.   },
  5568.   
  5569.   // Parses Firefox 1.0RC1+ update.rdf format
  5570.   _parseV20UpdateInfo: function(aDataSource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5571.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5572.  
  5573.     var updatesArc = gRDF.GetResource(EM_NS("updates"));
  5574.     var updates = aDataSource.GetTarget(extensionRes, updatesArc, true);
  5575.     
  5576.     try {
  5577.       updates = updates.QueryInterface(Components.interfaces.nsIRDFResource);
  5578.     }
  5579.     catch (e) { 
  5580.       LOG("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" + 
  5581.           aLocalItem.id + "\r\n" + 
  5582.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5583.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5584.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5585.           "\r\nTry checking that: \r\n" + 
  5586.           " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" + 
  5587.           "    RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" + 
  5588.           "    the <em:updates> listed all have matching GUIDs.");
  5589.       return; 
  5590.     }
  5591.     
  5592.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  5593.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  5594.     if (cu.IsContainer(aDataSource, updates)) {
  5595.       var ctr = getContainer(aDataSource, updates);
  5596.  
  5597.       // In "all update types" mode, we look for newer versions, starting with the 
  5598.       // current installed version.
  5599.       if (!aVersionUpdatesOnly) 
  5600.         aUpdateData.version = aLocalItem.version;
  5601.  
  5602.       var versions = ctr.GetElements();
  5603.       while (versions.hasMoreElements()) {
  5604.         // There are two different methodologies for collecting version 
  5605.         // information depending on whether or not we've bene invoked in 
  5606.         // "version updates only" mode or "version+newest" mode. 
  5607.         var version = versions.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5608.         this._parseV20Update(aDataSource, version, aLocalItem, aUpdateData, aVersionUpdatesOnly);
  5609.         if (aVersionUpdatesOnly && aUpdateData.updateLink)
  5610.           break;
  5611.       }
  5612.     }
  5613.   },
  5614.   
  5615.   _parseV20Update: function(aDataSource, aUpdateResource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5616.     var version = this._getPropertyFromResource(aDataSource, aUpdateResource, 
  5617.                                                 "version", aLocalItem);
  5618.     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
  5619.     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
  5620.     while (targetApps.hasMoreElements()) {
  5621.       var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5622.       var id = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
  5623.       if (id != this._updater._appID)
  5624.         continue;
  5625.       
  5626.       var result = gVersionChecker.compare(version, aLocalItem.version);
  5627.       if (aVersionUpdatesOnly ? result == 0 : result > 0) {
  5628.         aUpdateData.version = version;
  5629.         aUpdateData.updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
  5630.         aUpdateData.updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
  5631.         aUpdateData.minVersion = this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem);
  5632.         aUpdateData.maxVersion = this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem);
  5633.       }
  5634.     }
  5635.   }
  5636. };
  5637.  
  5638. /**
  5639.  * A Datasource that holds Extensions. 
  5640.  * - Implements nsIRDFDataSource to drive UI
  5641.  * - Uses a RDF/XML datasource for storage (this is undesirable)
  5642.  * 
  5643.  * @constructor
  5644.  */
  5645. function ExtensionsDataSource(em) {
  5646.   this._em = em;
  5647.   
  5648.   this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT);
  5649.   this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME);
  5650.   gRDF.RegisterDataSource(this, true);
  5651. }
  5652. ExtensionsDataSource.prototype = {
  5653.   _inner    : null,
  5654.   _em       : null,
  5655.   _itemRoot     : null,
  5656.   _defaultTheme : null,
  5657.   
  5658.   /**
  5659.    * Determine if an item is compatible
  5660.    * @param   datasource
  5661.    *          The datasource to inspect for compatibility - can be the main
  5662.    *          datasource or an Install Manifest.
  5663.    * @param   source
  5664.    *          The RDF Resource of the item to inspect for compatibility.
  5665.    * @param   version
  5666.    *          The version of the application we are checking for compatibility
  5667.    *          against. If this parameter is undefined, the version of the running
  5668.    *          application is used.
  5669.    * @returns true if the item is compatible with this version of the 
  5670.    *          application, false, otherwise.
  5671.    */
  5672.   isCompatible: function (datasource, source, version) {
  5673.     // The Default Theme is always compatible. 
  5674.     if (source.EqualsNode(this._defaultTheme))
  5675.       return true;
  5676.  
  5677.     if (version === undefined) {
  5678.       version = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  5679.                         gApp.version);
  5680.     }              
  5681.     var appID = gApp.ID;
  5682.     
  5683.     var targets = datasource.GetTargets(source, EM_R("targetApplication"), true);
  5684.     var idRes = EM_R("id");
  5685.     var minVersionRes = EM_R("minVersion");
  5686.     var maxVersionRes = EM_R("maxVersion");
  5687.     while (targets.hasMoreElements()) {
  5688.       var targetApp = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5689.       var id          = stringData(datasource.GetTarget(targetApp, idRes, true));
  5690.       var minVersion  = stringData(datasource.GetTarget(targetApp, minVersionRes, true));
  5691.       var maxVersion  = stringData(datasource.GetTarget(targetApp, maxVersionRes, true));
  5692.       if (id == appID) {
  5693.         var versionChecker = getVersionChecker();
  5694.         return ((versionChecker.compare(version, minVersion) >= 0) &&
  5695.                 (versionChecker.compare(version, maxVersion) <= 0));
  5696.       }
  5697.     }
  5698.     return false;
  5699.   },
  5700.   
  5701.   /**
  5702.    * Gets a list of items that are incompatible with a specific application version.
  5703.    * @param   appID
  5704.    *          The ID of the application - XXXben unused?
  5705.    * @param   appVersion
  5706.    *          The Version of the application to check for incompatibility against.
  5707.    * @param   desiredType
  5708.    *          The nsIUpdateItem type of items to look for
  5709.    * @param   includeDisabled
  5710.    *          Whether or not disabled items should be included in the set returned
  5711.    * @returns An array of nsIUpdateItems that are incompatible with the application
  5712.    *          ID/Version supplied.
  5713.    */
  5714.   getIncompatibleItemList: function(appID, appVersion, desiredType, includeDisabled) {
  5715.     var items = [];
  5716.     var ctr = getContainer(this._inner, this._itemRoot);
  5717.     var elements = ctr.GetElements();
  5718.     while (elements.hasMoreElements()) {
  5719.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5720.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  5721.       var type = this.getItemProperty(id, "type");
  5722.       // Skip this item if we're not seeking disabled items
  5723.       if (!includeDisabled &&
  5724.          (this.getItemProperty(id, "disabled") == "true" ||
  5725.           this.getItemProperty(id, "appDisabled") == "true"))
  5726.         continue;
  5727.       
  5728.       // If the id of this item matches one of the items potentially installed
  5729.       // with and maintained by this application AND it is installed in the 
  5730.       // global install location (i.e. the place installed by the app installer)
  5731.       // it is and can be managed by the update file - it's not an item that has
  5732.       // been manually installed by the user into their profile dir, and as such
  5733.       // it is always compatible with the next release of the application since
  5734.       // we will continue to support it.
  5735.       var locationKey = this.getItemProperty(id, "installLocation");
  5736.       var appManaged = this.getItemProperty(id, "appManaged") == "true";
  5737.       if (appManaged && locationKey == KEY_APP_GLOBAL)
  5738.         continue;
  5739.  
  5740.       if (type != -1 && (type & desiredType) && 
  5741.           !this.isCompatible(this, item, appVersion))
  5742.         items.push(this.getItemForID(id));
  5743.     }
  5744.     return items;
  5745.   },
  5746.   
  5747.   /**
  5748.    * Gets a list of items of a specific type
  5749.    * @param   desiredType
  5750.    *          The nsIUpdateItem type of items to return
  5751.    * @param   countRef
  5752.    *          The XPCJS reference to the size of the returned array
  5753.    * @returns An array of nsIUpdateItems, populated only with an item for |id|
  5754.    *          if |id| is non-null, otherwise all items matching the specified
  5755.    *          type.
  5756.    */
  5757.   getItemList: function(desiredType, countRef) {
  5758.     var items = [];
  5759.     var ctr = getContainer(this, this._itemRoot);      
  5760.     var elements = ctr.GetElements();
  5761.     while (elements.hasMoreElements()) {
  5762.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5763.       var eID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5764.       var type = this.getItemProperty(eID, "type");
  5765.       if (type != -1 && type & desiredType)
  5766.         items.push(this.getItemForID(eID));
  5767.     }
  5768.     countRef.value = items.length;
  5769.     return items;
  5770.   },
  5771.  
  5772.   /**
  5773.    * Get a list of Item IDs that have a flag set
  5774.    * @param   flag
  5775.    *          The name of an RDF property (less EM_NS) to check for
  5776.    * @param   desiredType
  5777.    *          The nsIUpdateItem type of item to look for
  5778.    * @returns An array of Item IDs 
  5779.    *
  5780.    * XXXben - this function is a little weird since it returns an array of 
  5781.    *          strings, not an array of nsIUpdateItems...  
  5782.    */
  5783.   getItemsWithFlagUnset: function(flag, desiredType) {
  5784.     var items = [];
  5785.  
  5786.     var ctr = getContainer(this, this._itemRoot);    
  5787.     var elements = ctr.GetElements();
  5788.     while (elements.hasMoreElements()) {
  5789.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5790.       var id = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5791.       var type = this.getItemProperty(id, "type");
  5792.       if (type != -1 && type & desiredType) {
  5793.         var value = this.GetTarget(e, EM_R(flag), true);
  5794.         if (!value)
  5795.           items.push(id);
  5796.       }
  5797.     }
  5798.     return items;
  5799.   },
  5800.   
  5801.   /**
  5802.    * Constructs an nsIUpdateItem for the given item ID
  5803.    * @param   id
  5804.    *          The GUID of the item to construct a nsIUpdateItem for
  5805.    * @returns The nsIUpdateItem for the id.
  5806.    */  
  5807.   getItemForID: function(id) {
  5808.     var r = getResourceForID(id);
  5809.     if (!r)
  5810.       return null;
  5811.     
  5812.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  5813.     var updateHash = this.getItemProperty(id, "availableUpdateHash");
  5814.     return makeItem(id, 
  5815.                     this.getItemProperty(id, "version"), 
  5816.                     this.getItemProperty(id, "installLocation"),
  5817.                     targetAppInfo ? targetAppInfo.minVersion : "",
  5818.                     targetAppInfo ? targetAppInfo.maxVersion : "",
  5819.                     this.getItemProperty(id, "name"),
  5820.                     this.getItemProperty(id, "availableUpdateURL"),
  5821.                     updateHash ? updateHash : "",
  5822.                     this.getItemProperty(id, "iconURL"), 
  5823.                     this.getItemProperty(id, "updateURL"), 
  5824.                     this.getItemProperty(id, "type"));
  5825.   },
  5826.   
  5827.   /**
  5828.    * Gets the name of the Install Location where an item is installed.
  5829.    * @param   id
  5830.    *          The GUID of the item to locate an Install Location for
  5831.    * @returns The string name of the Install Location where the item is 
  5832.    *          installed.
  5833.    */
  5834.   getInstallLocationKey: function(id) {
  5835.     return this.getItemProperty(id, "installLocation");
  5836.   },
  5837.   
  5838.   /**
  5839.    * Sets an RDF property on an item in a datasource. Does not create
  5840.    * multiple assertions
  5841.    * @param   datasource
  5842.    *          The target datasource where the property should be set
  5843.    * @param   source
  5844.    *          The RDF Resource to set the property on
  5845.    * @param   property
  5846.    *          The RDF Resource of the property to set
  5847.    * @param   newValue
  5848.    *          The RDF Node containing the new property value
  5849.    */
  5850.   _setProperty: function(datasource, source, property, newValue) {
  5851.     var oldValue = datasource.GetTarget(source, property, true);
  5852.     if (oldValue) {
  5853.       if (newValue)
  5854.         datasource.Change(source, property, oldValue, newValue);
  5855.       else
  5856.         datasource.Unassert(source, property, oldValue);
  5857.     }
  5858.     else if (newValue)
  5859.       datasource.Assert(source, property, newValue, true);
  5860.   },
  5861.   
  5862.   /**
  5863.    * Sets the target application info for an item in the Extensions
  5864.    * datasource and in the item's install manifest if it is installed in a
  5865.    * profile's extensions directory, it exists, and we have write access.
  5866.    * @param   id
  5867.    *          The ID of the item to update target application info for
  5868.    * @param   minVersion
  5869.    *          The minimum version of the target application that this item can
  5870.    *          run in
  5871.    * @param   maxVersion
  5872.    *          The maximum version of the target application that this item can
  5873.    *          run in
  5874.    */
  5875.   updateTargetAppInfo: function(id, minVersion, maxVersion)
  5876.   {
  5877.     // Update the Extensions datasource
  5878.     this.setTargetApplicationInfo(id, minVersion, maxVersion, null);
  5879.  
  5880.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  5881.     if (installLocation.name != KEY_APP_PROFILE)
  5882.       return;
  5883.  
  5884.     var installManifestFile = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  5885.     // Only update if the item exists and we can write to the location
  5886.     if (installManifestFile.exists() && installLocation.canAccess)
  5887.       this.setTargetApplicationInfo(id, minVersion, maxVersion,
  5888.                                     getInstallManifest(installManifestFile));
  5889.   },
  5890.  
  5891.   /**
  5892.    * Gets the updated target application info if it exists for an item from
  5893.    * the Extensions datasource during an installation or upgrade.
  5894.    * @param   id
  5895.    *          The ID of the item to discover updated target application info for
  5896.    * @returns A JS Object with the following properties:
  5897.    *          "id"            The id of the item
  5898.    *          "minVersion"    The updated minimum version of the target
  5899.    *                          application that this item can run in
  5900.    *          "maxVersion"    The updated maximum version of the target
  5901.    *                          application that this item can run in
  5902.    */
  5903.   getUpdatedTargetAppInfo: function(id) {
  5904.     // The default theme is always compatible so there is never update info.
  5905.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5906.       return null;
  5907.  
  5908.     var appID = gApp.ID;
  5909.     var r = getResourceForID(id);
  5910.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5911.     if (!targetApps.hasMoreElements())
  5912.       targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5913.     while (targetApps.hasMoreElements()) {
  5914.       var targetApp = targetApps.getNext();
  5915.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5916.         try {
  5917.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5918.           if (foundAppID != appID) // Different target application
  5919.             continue;
  5920.           var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true);
  5921.           var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true);
  5922.           if (updatedMinVersion && updatedMaxVersion)
  5923.             return { id        : id,
  5924.                      minVersion: stringData(updatedMinVersion),
  5925.                      maxVersion: stringData(updatedMaxVersion) };
  5926.           else
  5927.             return null;
  5928.         }
  5929.         catch (e) { 
  5930.           continue;
  5931.         }
  5932.       }
  5933.     }
  5934.     return null;
  5935.   },
  5936.   
  5937.   /**
  5938.    * Sets the updated target application info for an item in the Extensions
  5939.    * datasource during an installation or upgrade.
  5940.    * @param   id
  5941.    *          The ID of the item to set updated target application info for
  5942.    * @param   updatedMinVersion
  5943.    *          The updated minimum version of the target application that this
  5944.    *          item can run in
  5945.    * @param   updatedMaxVersion
  5946.    *          The updated maximum version of the target application that this
  5947.    *          item can run in
  5948.    */
  5949.   setUpdatedTargetAppInfo: function(id, updatedMinVersion, updatedMaxVersion) {
  5950.     // The default theme is always compatible so it is never updated.
  5951.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5952.       return;
  5953.  
  5954.     // Version/Dependency Info
  5955.     var updatedMinVersionRes = EM_R("updatedMinVersion");
  5956.     var updatedMaxVersionRes = EM_R("updatedMaxVersion");
  5957.  
  5958.     var appID = gApp.ID;
  5959.     var r = getResourceForID(id);
  5960.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5961.     // add updatedMinVersion and updatedMaxVersion for an install else an upgrade
  5962.     if (!targetApps.hasMoreElements()) {
  5963.       var idRes = EM_R("id");
  5964.       var targetRes = getResourceForID(id);
  5965.       var property = EM_R("targetApplication");
  5966.       var anon = gRDF.GetAnonymousResource();
  5967.       this._inner.Assert(anon, idRes, EM_L(appID), true);
  5968.       this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5969.       this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5970.       this._inner.Assert(targetRes, property, anon, true);
  5971.     }
  5972.     else {
  5973.       while (targetApps.hasMoreElements()) {
  5974.         var targetApp = targetApps.getNext();
  5975.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5976.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5977.           if (foundAppID != appID) // Different target application
  5978.             continue;
  5979.           this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5980.           this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5981.           break;
  5982.         }
  5983.       }
  5984.     }
  5985.     this.Flush();
  5986.   },
  5987.  
  5988.   /**
  5989.    * Gets the target application info for an item from a datasource.
  5990.    * @param   id
  5991.    *          The GUID of the item to discover target application info for
  5992.    * @param   datasource
  5993.    *          The datasource to look up target application info in
  5994.    * @returns A JS Object with the following properties:
  5995.    *          "minVersion"    The minimum version of the target application
  5996.    *                          that this item can run in
  5997.    *          "maxVersion"    The maximum version of the target application
  5998.    *                          that this item can run in
  5999.    *          or null, if no target application data exists for the specified
  6000.    *          id in the supplied datasource.
  6001.    */
  6002.   getTargetApplicationInfo: function(id, datasource) {
  6003.     // The default theme is always compatible. 
  6004.     if (getResourceForID(id).EqualsNode(this._defaultTheme)) {
  6005.       var ver = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  6006.                         gApp.version);
  6007.       return { minVersion: ver, maxVersion: ver };
  6008.     }
  6009.     var appID = gApp.ID;
  6010.     var r = getResourceForID(id);
  6011.     var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true);
  6012.     if (!targetApps)
  6013.       return null;
  6014.     if (!targetApps.hasMoreElements())
  6015.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  6016.     while (targetApps.hasMoreElements()) {
  6017.       var targetApp = targetApps.getNext();
  6018.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  6019.         try {
  6020.           var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true));
  6021.           if (foundAppID != appID) // Different target application
  6022.             continue;
  6023.           
  6024.           return { minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)),
  6025.                    maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true)) };
  6026.         }
  6027.         catch (e) { 
  6028.           continue;
  6029.         }
  6030.       }
  6031.     }
  6032.     return null;
  6033.   },
  6034.   
  6035.   /**
  6036.    * Sets the target application info for an item in a datasource.
  6037.    * @param   id
  6038.    *          The GUID of the item to discover target application info for
  6039.    * @param   minVersion
  6040.    *          The minimum version of the target application that this item can
  6041.    *          run in
  6042.    * @param   maxVersion
  6043.    *          The maximum version of the target application that this item can
  6044.    *          run in
  6045.    * @param   datasource
  6046.    *          The datasource to loko up target application info in
  6047.    */
  6048.   setTargetApplicationInfo: function(id, minVersion, maxVersion, datasource) {
  6049.     var targetDataSource = datasource;
  6050.     if (!targetDataSource)
  6051.       targetDataSource = this._inner;
  6052.       
  6053.     var appID = gApp.ID;
  6054.     var r = getResourceForID(id);
  6055.     var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true);
  6056.     if (!targetApps.hasMoreElements())
  6057.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  6058.     while (targetApps.hasMoreElements()) {
  6059.       var targetApp = targetApps.getNext();
  6060.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  6061.         var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true));
  6062.         if (foundAppID != appID) // Different target application
  6063.           continue;
  6064.         
  6065.         this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion));
  6066.         this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion));
  6067.         
  6068.         // If we were setting these properties on the main datasource, flush
  6069.         // it now. (Don't flush changes set on Install Manifests - they are
  6070.         // fleeting).
  6071.         if (!datasource)
  6072.           this.Flush();
  6073.  
  6074.         break;
  6075.       }
  6076.     }
  6077.   },
  6078.   
  6079.   /** 
  6080.    * Gets a property of an item
  6081.    * @param   id
  6082.    *          The GUID of the item
  6083.    * @param   property
  6084.    *          The name of the property (excluding EM_NS)
  6085.    * @returns The literal value of the property, or undefined if there is no 
  6086.    *          value.
  6087.    */
  6088.   getItemProperty: function(id, property) { 
  6089.     var item = getResourceForID(id);
  6090.     if (!item) {
  6091.       LOG("getItemProperty failing for lack of an item. This means getResourceForItem \
  6092.            failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")");
  6093.     }
  6094.     else 
  6095.       return this._getItemProperty(item, property);
  6096.     return undefined;
  6097.   },
  6098.   
  6099.   /**
  6100.    * Gets a property of an item resource
  6101.    * @param   itemResource
  6102.    *          The RDF Resource of the item
  6103.    * @param   property
  6104.    *          The name of the property (excluding EM_NS)
  6105.    * @returns The literal value of the property, or undefined if there is no
  6106.    *          value.
  6107.    */
  6108.   _getItemProperty: function(itemResource, property) {
  6109.     var target = this.GetTarget(itemResource, EM_R(property), true);
  6110.     var value = stringData(target);
  6111.     if (value === undefined)
  6112.       value = intData(target);
  6113.     return value === undefined ? "" : value;
  6114.   },
  6115.   
  6116.   /**
  6117.    * Sets a property on an item.
  6118.    * @param   id
  6119.    *          The GUID of the item
  6120.    * @param   propertyArc
  6121.    *          The RDF Resource of the property arc
  6122.    * @param   propertyValue
  6123.    *          A nsIRDFLiteral value of the property to be set
  6124.    */
  6125.   setItemProperty: function (id, propertyArc, propertyValue) {
  6126.     var item = getResourceForID(id);
  6127.     this._setProperty(this._inner, item, propertyArc, propertyValue);
  6128.     this.Flush();  
  6129.   },
  6130.  
  6131.   /**
  6132.    * Inserts the RDF resource for an item into a container.
  6133.    * @param   id
  6134.    *          The GUID of the item
  6135.    */
  6136.   insertItemIntoContainer: function(id) {
  6137.     // Get the target container and resource
  6138.     var ctr = getContainer(this._inner, this._itemRoot);
  6139.     var itemResource = getResourceForID(id);
  6140.     // Don't bother adding the extension to the list if it's already there. 
  6141.     // (i.e. we're upgrading)
  6142.     var oldIndex = ctr.IndexOf(itemResource);
  6143.     if (oldIndex == -1)
  6144.       ctr.AppendElement(itemResource);
  6145.     this.Flush();
  6146.   }, 
  6147.  
  6148.   /**
  6149.    * Removes the RDF resource for an item from its container.
  6150.    * @param   id
  6151.    *          The GUID of the item
  6152.    */
  6153.   removeItemFromContainer: function(id) {
  6154.     var ctr = getContainer(this._inner, this._itemRoot);
  6155.     var itemResource = getResourceForID(id);
  6156.     ctr.RemoveElement(itemResource, true);
  6157.     this.Flush();
  6158.   },
  6159.  
  6160.   /**
  6161.    * Removes a corrupt item entry from the extension list added due to buggy 
  6162.    * code in previous EM versions!  
  6163.    * @param   id
  6164.    *          The GUID of the item
  6165.    */
  6166.   removeCorruptItem: function(id) {
  6167.     this.removeItemMetadata(id);
  6168.     this.removeItemFromContainer(id);
  6169.   },
  6170.  
  6171.   /**
  6172.    * Removes a corrupt download entry from the list
  6173.    * @param   uri
  6174.    *          The RDF URI of the item.
  6175.    * @returns The RDF Resource of the removed entry 
  6176.    */
  6177.   removeCorruptDLItem: function(uri) {
  6178.     var itemResource = gRDF.GetResource(uri);
  6179.     var ctr = getContainer(this._inner, this._itemRoot);
  6180.     if (ctr.IndexOf(itemResource) != -1) {
  6181.       ctr.RemoveElement(itemResource, true);
  6182.       this._cleanResource(itemResource);
  6183.       this.Flush();
  6184.     }
  6185.     return itemResource;
  6186.   },
  6187.   
  6188.   /**
  6189.    * Copies metadata from an Install Manifest Datasource into the Extensions
  6190.    * DataSource.
  6191.    * @param   id
  6192.    *          The GUID of the item
  6193.    * @param   installManifest
  6194.    *          The Install Manifest datasource we are copying from
  6195.    * @param   installLocation
  6196.    *          The Install Location of the item. 
  6197.    */
  6198.   addItemMetadata: function(id, installManifest, installLocation) {
  6199.     // Copy the assertions over from the source datasource. 
  6200.     var targetRes = getResourceForID(id);
  6201.     // Assert properties with single values
  6202.     var singleProps = ["version", "name", "description", "creator", "homepageURL", 
  6203.                        "updateURL", "updateService", "optionsURL", "aboutURL", 
  6204.                        "iconURL", "internalName"];
  6205.  
  6206.     // Items installed into restricted Install Locations can also be locked 
  6207.     // (can't be removed or disabled), and hidden (not shown in the UI)
  6208.     if (installLocation.restricted)
  6209.       singleProps = singleProps.concat(["locked", "hidden"]);
  6210.     if (installLocation.name == KEY_APP_GLOBAL) 
  6211.       singleProps = singleProps.concat(["appManaged"]);
  6212.     for (var i = 0; i < singleProps.length; ++i) {
  6213.       var property = EM_R(singleProps[i]);
  6214.       var literal = installManifest.GetTarget(gInstallManifestRoot, property, true);
  6215.       // If literal is null, _setProperty will remove any existing.
  6216.       this._setProperty(this._inner, targetRes, property, literal);
  6217.     }    
  6218.     
  6219.     // Assert properties with multiple values    
  6220.     var manyProps = ["contributor"];
  6221.     for (var i = 0; i < manyProps.length; ++i) {
  6222.       var property = EM_R(manyProps[i]);
  6223.       var literals = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6224.       
  6225.       var oldValues = this._inner.GetTargets(targetRes, property, true);
  6226.       while (oldValues.hasMoreElements()) {
  6227.         var oldValue = oldValues.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6228.         this._inner.Unassert(targetRes, property, oldValue);
  6229.       }
  6230.       while (literals.hasMoreElements()) {
  6231.         var literal = literals.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6232.         this._inner.Assert(targetRes, property, literal, true);
  6233.       }
  6234.     }
  6235.  
  6236.     // Version/Dependency Info
  6237.     var versionProps = ["targetApplication", "requires"];
  6238.     var idRes = EM_R("id");
  6239.     var minVersionRes = EM_R("minVersion");
  6240.     var maxVersionRes = EM_R("maxVersion");
  6241.     for (var i = 0; i < versionProps.length; ++i) {
  6242.       var property = EM_R(versionProps[i]);
  6243.       var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6244.  
  6245.       var oldVersionInfos = this._inner.GetTargets(targetRes, property, true);
  6246.       while (oldVersionInfos.hasMoreElements()) {
  6247.         var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6248.         this._cleanResource(oldVersionInfo);
  6249.         this._inner.Unassert(targetRes, property, oldVersionInfo);
  6250.       }
  6251.       while (newVersionInfos.hasMoreElements()) {
  6252.         var newVersionInfo = newVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6253.         var anon = gRDF.GetAnonymousResource();
  6254.         this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true);
  6255.         this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true);
  6256.         this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true);
  6257.         this._inner.Assert(targetRes, property, anon, true);
  6258.       }
  6259.     }
  6260.     this.updateProperty(id, "opType");
  6261.     this.updateProperty(id, "updateable");
  6262.     this.updateProperty(id, "displayDescription");
  6263.     this.Flush();
  6264.   },
  6265.   
  6266.   /**
  6267.    * Strips an item entry of all assertions.
  6268.    * @param   id
  6269.    *          The GUID of the item
  6270.    */
  6271.   removeItemMetadata: function(id) {
  6272.     var item = getResourceForID(id);
  6273.     var resources = ["targetApplication", "requires"];
  6274.     for (var i = 0; i < resources.length; ++i) {
  6275.       var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true);
  6276.       while (targetApps.hasMoreElements()) {
  6277.         var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6278.         this._cleanResource(targetApp);
  6279.       }
  6280.     }
  6281.  
  6282.     this._cleanResource(item);
  6283.   },
  6284.   
  6285.   /**
  6286.    * Strips a resource of all outbound assertions. We use methods like this 
  6287.    * since the RDFXMLDatasource will write out all assertions, even if they
  6288.    * are not connected through our root. 
  6289.    * @param   resource
  6290.    *          The resource to clean. 
  6291.    */
  6292.   _cleanResource: function(resource) {
  6293.     // Remove outward arcs
  6294.     var arcs = this._inner.ArcLabelsOut(resource);
  6295.     while (arcs.hasMoreElements()) {
  6296.       var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6297.       var targets = this._inner.GetTargets(resource, arc, true);
  6298.       while (targets.hasMoreElements()) {
  6299.         var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6300.         if (value)
  6301.           this._inner.Unassert(resource, arc, value);
  6302.       }
  6303.     }
  6304.   },
  6305.   
  6306.   /**
  6307.    * Notify views that this propery has changed (this is for properties that
  6308.    * are implemented by this datasource rather than by the inner in-memory
  6309.    * datasource and thus do not get free change handling).
  6310.    * @param   id 
  6311.    *          The GUID of the item to update the property for.
  6312.    * @param   property
  6313.    *          The property (less EM_NS) to update.
  6314.    */
  6315.   updateProperty: function(id, property) {
  6316.     var item = getResourceForID(id);
  6317.     var propertyResource = EM_R(property);
  6318.     var value = this.GetTarget(item, propertyResource, true);
  6319.     if (item && value) {
  6320.       for (var i = 0; i < this._observers.length; ++i)
  6321.         this._observers[i].onChange(this, item, propertyResource, 
  6322.                                     EM_L(""), value);
  6323.     }
  6324.   },
  6325.   
  6326.   /**
  6327.    * Move an Item to the index of another item in its container.
  6328.    * @param   movingID
  6329.    *          The ID of the item to be moved.
  6330.    * @param   destinationID
  6331.    *          The ID of an item to move another item to.
  6332.    */
  6333.   moveToIndexOf: function(movingID, destinationID) {
  6334.     var extensions = gRDF.GetResource(RDFURI_ITEM_ROOT);
  6335.     var ctr = getContainer(this._inner, extensions);
  6336.     var item = gRDF.GetResource(movingID);
  6337.     var index = ctr.IndexOf(gRDF.GetResource(destinationID));
  6338.     if (index == -1)
  6339.       index = 1; // move to the beginning if destinationID is not found
  6340.     this._inner.beginUpdateBatch();
  6341.     ctr.RemoveElement(item, true);
  6342.     ctr.InsertElementAt(item, index, true);
  6343.     this._inner.endUpdateBatch();
  6344.     this.Flush();
  6345.   },
  6346.  
  6347.   /**
  6348.    * Determines if an Item is an active download
  6349.    * @param   id
  6350.    *          The GUID of the item
  6351.    * @returns true if the item is an active download, false otherwise.
  6352.    */
  6353.   isDownloadItem: function(id) {
  6354.     return this.getItemProperty(id, "downloadURL") != "";
  6355.   },
  6356.  
  6357.   /**
  6358.    * Adds an entry representing an active download to the appropriate container
  6359.    * @param   addon
  6360.    *          An object implementing nsIUpdateItem for the addon being 
  6361.    *          downloaded.
  6362.    */
  6363.   addDownload: function(addon) {
  6364.     if (addon.id != addon.xpiURL)
  6365.       return;
  6366.     var res = gRDF.GetResource(addon.xpiURL);
  6367.     this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name));
  6368.     this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version));
  6369.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL));
  6370.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL));
  6371.     this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type));
  6372.  
  6373.     var ctr = getContainer(this._inner, this._itemRoot);
  6374.     if (ctr.IndexOf(res) == -1)
  6375.       ctr.AppendElement(res);
  6376.     
  6377.     this.Flush();
  6378.   },
  6379.   
  6380.   /**
  6381.    * Adds an entry representing an item that is incompatible and is being
  6382.    * checked for a compatibility update.
  6383.    * @param   name
  6384.    *          The display name of the item being checked
  6385.    * @param   url
  6386.    *          The URL string of the xpi file that has been staged. This is
  6387.    *          also used for installLocation to make this an independently
  6388.    *          managed item
  6389.    * @param   type
  6390.    *          The nsIUpdateItem type of the item
  6391.    * @param   version
  6392.    *          The version of the item
  6393.    */
  6394.   addIncompatibleUpdateItem: function(name, url, type, version) {
  6395.     // type must be TYPE_EXTENSION for a multi_xpi to display in the manager.
  6396.     if (type == nsIUpdateItem.TYPE_MULTI_XPI)
  6397.       type = nsIUpdateItem.TYPE_EXTENSION;
  6398.  
  6399.     var iconURL = (type == nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME :
  6400.                                                        URI_GENERIC_ICON_XPINSTALL;
  6401.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6402.     var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage",
  6403.                                                            [BundleManager.appName, name], 2)
  6404.  
  6405.     var res = gRDF.GetResource(url);
  6406.     this._setProperty(this._inner, res, EM_R("name"), EM_L(name));
  6407.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL));
  6408.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url));
  6409.     this._setProperty(this._inner, res, EM_R("type"), EM_I(type));
  6410.     this._setProperty(this._inner, res, EM_R("version"), EM_L(version));
  6411.     this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true"));
  6412.     this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg));
  6413.  
  6414.     var ctr = getContainer(this._inner, this._itemRoot);
  6415.     if (ctr.IndexOf(res) == -1)
  6416.       ctr.AppendElement(res);
  6417.  
  6418.     this.Flush();
  6419.   },
  6420.  
  6421.   /**
  6422.    * Removes an active download from the appropriate container
  6423.    * @param   url
  6424.    *          The URL string of the active download to be removed
  6425.    */
  6426.   removeDownload: function(url) {
  6427.     var res = gRDF.GetResource(url);
  6428.     var ctr = getContainer(this._inner, this._itemRoot);
  6429.     if (ctr.IndexOf(res) != -1) 
  6430.       ctr.RemoveElement(res, true);
  6431.     this._cleanResource(res);
  6432.     this.Flush();
  6433.   },
  6434.   
  6435.   /**
  6436.    * Write download progress info for a set of items to the Datasource
  6437.    * @param   data
  6438.    *          A JS Object containing progress information for a set of active
  6439.    *          downloads, hashed by URL. Each object has the following properties:
  6440.    *          "state"     An integer value representing the download/install
  6441.    *                      state.
  6442.    *          "progress"  An integer value between 0 and 100 representing 
  6443.    *                      percentage complete
  6444.    */
  6445.   flushProgressInfo: function(data) {
  6446.     for (var url in data) {
  6447.       var res = gRDF.GetResource(url);
  6448.       this._setProperty(this._inner, res, EM_R("state"), EM_I(data[url].state));
  6449.       this._setProperty(this._inner, res, EM_R("progress"), EM_I(data[url].progress));
  6450.     }
  6451.     this.Flush();
  6452.   },   
  6453.   
  6454.   /**
  6455.    * A GUID->location-key hash of items that are visible to the application.
  6456.    * These are items that show up in the Extension/Themes etc UI. If there is
  6457.    * an instance of the same item installed in Install Locations of differing 
  6458.    * profiles, the item at the highest priority location will appear in this 
  6459.    * list.
  6460.    */
  6461.   visibleItems: { },
  6462.   
  6463.   /**
  6464.    * Walk the list of installed items and determine what the visible list is, 
  6465.    * based on which items are visible at the highest priority locations. 
  6466.    */  
  6467.   _buildVisibleItemList: function() {
  6468.     var ctr = getContainer(this, this._itemRoot);
  6469.     var items = ctr.GetElements();
  6470.     while (items.hasMoreElements()) {
  6471.       var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6472.       // Resource URIs adopt the format: location-key,item-id
  6473.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6474.       this.visibleItems[id] = this.getItemProperty(id, "installLocation");
  6475.     }
  6476.   },
  6477.   
  6478.   /**
  6479.    * Updates an item's location in the visible item list.
  6480.    * @param   id
  6481.    *          The GUID of the item to update
  6482.    * @param   locationKey
  6483.    *          The name of the Install Location where the item is installed.
  6484.    * @param   forceReplace
  6485.    *          true if the new location should be used, regardless of its 
  6486.    *          priority relationship to existing entries, false if the location
  6487.    *          should only be updated if its priority is lower than the existing
  6488.    *          value.
  6489.    */
  6490.   updateVisibleList: function(id, locationKey, forceReplace) {
  6491.     if (id in this.visibleItems && this.visibleItems[id]) {
  6492.       var oldLocation = InstallLocations.get(this.visibleItems[id]);
  6493.       var newLocation = InstallLocations.get(locationKey);
  6494.       if (forceReplace || newLocation.priority < oldLocation.priority) 
  6495.         this.visibleItems[id] = locationKey;
  6496.     }
  6497.     else 
  6498.       this.visibleItems[id] = locationKey;
  6499.   },
  6500.  
  6501.   /**
  6502.    * Load the Extensions Datasource from disk.
  6503.    */
  6504.   loadExtensions: function() {
  6505.     var extensionsFile  = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  6506.     this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile));
  6507.  
  6508.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  6509.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  6510.     cu.MakeSeq(this._inner, this._itemRoot);
  6511.  
  6512.     this._buildVisibleItemList();
  6513.   },
  6514.   
  6515.   /**
  6516.    * A hash of Addon IDs that we are currently looking for updates to. 
  6517.    */
  6518.   _updateURLs: { },
  6519.   
  6520.   /**
  6521.    * See nsIExtensionManager.idl
  6522.    */
  6523.   onUpdateStarted: function() {
  6524.     LOG("Datasource: Update Started");
  6525.   },
  6526.   
  6527.   /**
  6528.    * See nsIExtensionManager.idl
  6529.    */
  6530.   onUpdateEnded: function() {
  6531.     LOG("Datasource: Update Ended");
  6532.     this._updateURLs = { };
  6533.   },
  6534.   
  6535.   /**
  6536.    * See nsIExtensionManager.idl
  6537.    */
  6538.   onAddonUpdateStarted: function(addon) {
  6539.     LOG("Datasource: Addon Update Started: " + addon.id);
  6540.     this._updateURLs[addon.id] = addon.id;
  6541.     this.updateProperty(addon.id, "availableUpdateURL");
  6542.     this.updateProperty(addon.id, "displayDescription");
  6543.   },
  6544.   
  6545.   /**
  6546.    * See nsIExtensionManager.idl
  6547.    */
  6548.   onAddonUpdateEnded: function(addon, status) {
  6549.     LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status);
  6550.     this._updateURLs[addon.id] = status;
  6551.     var url = null, hash = null, version = null;
  6552.     var updateAvailable = status == nsIAddonUpdateCheckListener.STATUS_UPDATE;
  6553.     if (updateAvailable) {
  6554.       url = EM_L(addon.xpiURL);
  6555.       if (addon.xpiHash)
  6556.         hash = EM_L(addon.xpiHash);
  6557.       version = EM_L(addon.version);
  6558.     }
  6559.     this.setItemProperty(addon.id, EM_R("availableUpdateURL"), url);
  6560.     this.setItemProperty(addon.id, EM_R("availableUpdateHash"), hash);
  6561.     this.setItemProperty(addon.id, EM_R("availableUpdateVersion"), version);
  6562.     this.updateProperty(addon.id, "availableUpdateURL");
  6563.     this.updateProperty(addon.id, "displayDescription");
  6564.   },
  6565.  
  6566.   /////////////////////////////////////////////////////////////////////////////
  6567.   // nsIRDFDataSource
  6568.   get URI() {
  6569.     return "rdf:extensions";
  6570.   },
  6571.   
  6572.   GetSource: function(property, target, truthValue) {
  6573.     return this._inner.GetSource(property, target, truthValue);
  6574.   },
  6575.   
  6576.   GetSources: function(property, target, truthValue) {
  6577.     return this._inner.GetSources(property, target, truthValue);
  6578.   },
  6579.   
  6580.   /**
  6581.    * Gets an URL to a theme's image file
  6582.    * @param   item
  6583.    *          The RDF Resource representing the item 
  6584.    * @param   fileName
  6585.    *          The file to locate a URL for
  6586.    * @param   fallbackURL
  6587.    *          If the location fails, supply this URL instead
  6588.    * @returns An RDF Resource to the URL discovered, or the fallback
  6589.    *          if the discovery failed. 
  6590.    */
  6591.   _getThemeImageURL: function(item, fileName, fallbackURL) {
  6592.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6593.     var installLocation = this._em.getInstallLocation(id);
  6594.     var file = installLocation.getItemFile(id, fileName)
  6595.     if (file.exists())
  6596.       return gRDF.GetResource(getURLSpecFromFile(file));
  6597.  
  6598.     if (id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)) {
  6599.       var jarFile = getFile(KEY_APPDIR, [DIR_CHROME, FILE_DEFAULT_THEME_JAR]);
  6600.       var url = "jar:" + getURLSpecFromFile(jarFile) + "!/" + fileName;
  6601.       return gRDF.GetResource(url);
  6602.     }
  6603.  
  6604.     return fallbackURL ? gRDF.GetResource(fallbackURL) : null;
  6605.   },
  6606.  
  6607.   /**
  6608.    * Get the em:iconURL property (icon url of the item)
  6609.    */
  6610.   _rdfGet_iconURL: function(item, property) {
  6611.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6612.     var type = this.getItemProperty(id, "type");
  6613.     if (type != -1 && type & nsIUpdateItem.TYPE_EXTENSION) {
  6614.       var hasIconURL = this._inner.hasArcOut(item, property);
  6615.       // If the download entry doesn't have a IconURL property, use a
  6616.       // generic icon URL instead.
  6617.       if (!hasIconURL || this.getItemProperty(id, "disabled") == "true")
  6618.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6619.       var iconURL = this._inner.GetTarget(item, property, true);
  6620.       iconURL = iconURL.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  6621.       var uri = newURI(iconURL);
  6622.       try {
  6623.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  6624.                             .getService(Components.interfaces.nsIChromeRegistry);
  6625.         cr.convertChromeURL(uri);
  6626.       }
  6627.       catch(e) {
  6628.         // bogus URI, supply a generic icon. 
  6629.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6630.       }
  6631.     }
  6632.     else if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6633.       return this._getThemeImageURL(item, "icon.png", URI_GENERIC_ICON_THEME);
  6634.     return null;
  6635.   },
  6636.   
  6637.   /**
  6638.    * Get the em:previewImage property (preview image of the item)
  6639.    */
  6640.   _rdfGet_previewImage: function(item, property) {
  6641.     var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type");
  6642.     if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6643.       return this._getThemeImageURL(item, "preview.png", null);
  6644.     return null;
  6645.   },
  6646.   
  6647.   /**
  6648.    * If we're in safe mode, the item is disabled by the user or app, or the
  6649.    * item is to be upgraded force the generic about dialog for the item.
  6650.    */
  6651.   _rdfGet_aboutURL: function(item, property) {
  6652.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6653.     if (inSafeMode() || this.getItemProperty(id, "disabled") == "true" ||
  6654.         this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE)
  6655.       return EM_L("");
  6656.  
  6657.     return null;
  6658.   },
  6659.  
  6660.   /**
  6661.    * Get the em:compatible property (whether or not this item is compatible)
  6662.    */
  6663.   _rdfGet_compatible: function(item, property) {
  6664.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6665.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  6666.     if (!targetAppInfo)
  6667.       return EM_L("false");
  6668.     getVersionChecker();
  6669.     
  6670.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  6671.                              gApp.version);
  6672.     if (gVersionChecker.compare(targetAppInfo.maxVersion, appVersion) < 0 || 
  6673.         gVersionChecker.compare(appVersion, targetAppInfo.minVersion) < 0) {
  6674.       // OK, this item is incompatible. 
  6675.       return EM_L("false");
  6676.     }
  6677.     return EM_L("true");
  6678.   }, 
  6679.   
  6680.   /** 
  6681.    * Gets the em:availableUpdateURL - the URL to an XPI update package, if
  6682.    * present, or a literal string "novalue" if there is no update XPI URL.
  6683.    */
  6684.   _rdfGet_availableUpdateURL: function(item, property) {
  6685.     var value = this._inner.GetTarget(item, property, true);
  6686.     if (!value) 
  6687.       return EM_L("none");
  6688.     return value;
  6689.   },
  6690.  
  6691.   /**
  6692.    * Get the em:displayDescription property (description that is displayed 
  6693.    * in the UI - not always the info description of the item - sometimes
  6694.    * a message like "will be uninstalled after restart" etc)
  6695.    */ 
  6696.   _rdfGet_displayDescription: function(item, property) {
  6697.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6698.  
  6699.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6700.     var itemName = this.getItemProperty(id, "name");
  6701.     var opType = this.getItemProperty(id, "opType");
  6702.     
  6703.     function getLiteral(key, strings) {
  6704.       return EM_L(extensionsStrings.formatStringFromName(key, 
  6705.                   strings, strings.length));
  6706.     }
  6707.     if (id in this._updateURLs && this._updateURLs[id]) {
  6708.       switch (this._updateURLs[id]) {
  6709.       case id:
  6710.         return getLiteral("updatingMessage", [itemName]);
  6711.       case nsIAddonUpdateCheckListener.STATUS_APP_MANAGED:
  6712.       case nsIAddonUpdateCheckListener.STATUS_NO_UPDATE:
  6713.         return getLiteral("updateNoUpdateMessage", [itemName]);
  6714.       case nsIAddonUpdateCheckListener.STATUS_VERSIONINFO:
  6715.         if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL)
  6716.           break;
  6717.         return getLiteral("updateCompatibilityMessage", [itemName]);
  6718.       case nsIAddonUpdateCheckListener.STATUS_FAILURE:
  6719.         return getLiteral("updateErrorMessage", [itemName]);
  6720.       case nsIAddonUpdateCheckListener.STATUS_DISABLED:
  6721.         return getLiteral("updateDisabledMessage", [itemName]);
  6722.       case nsIAddonUpdateCheckListener.STATUS_READ_ONLY:
  6723.         return getLiteral("updateReadOnlyMessage", []);
  6724.       case nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED:
  6725.         return getLiteral("updateNotManagedMessage", [BundleManager.appName]);
  6726.       }
  6727.     }
  6728.     var node = this._inner.GetTarget(item, EM_R("availableUpdateURL"), true); 
  6729.     if (node) {
  6730.       var version = this.getItemProperty(id, "availableUpdateVersion");
  6731.       return getLiteral("updateAvailableMessage", [itemName, version]);
  6732.     }
  6733.     switch (opType) {
  6734.     case OP_NEEDS_DISABLE:
  6735.       return getLiteral("restartBeforeDisableMessage", [itemName, BundleManager.appName]);
  6736.     case OP_NEEDS_ENABLE:
  6737.       return getLiteral("restartBeforeEnableMessage", [itemName, BundleManager.appName]);
  6738.     case OP_NEEDS_INSTALL:
  6739.       return getLiteral("restartBeforeInstallMessage", [itemName, BundleManager.appName]);
  6740.     case OP_NEEDS_UNINSTALL:
  6741.       return getLiteral("restartBeforeUninstallMessage", [itemName, BundleManager.appName]);
  6742.     case OP_NEEDS_UPGRADE:
  6743.       return getLiteral("restartBeforeUpgradeMessage", [itemName, BundleManager.appName]);
  6744.     }
  6745.  
  6746.     if (this.getItemProperty(id, "appDisabled") == "true" &&
  6747.         this.getItemProperty(id, "compatible") != "true")
  6748.       return getLiteral("incompatibleExtension", [BundleManager.appName, gApp.version]);
  6749.  
  6750.     if (inSafeMode())
  6751.       return getLiteral("disabledBySafeMode", [itemName, BundleManager.appName]);
  6752.  
  6753.     // No special state for this item, so just use the "description" property. 
  6754.     return this.GetTarget(item, EM_R("description"), true);
  6755.   },
  6756.   
  6757.   /**
  6758.    * Get the em:opType property (controls widget state for the EM UI)
  6759.    * from the Startup Cache (e.g. extensions.cache)
  6760.    */
  6761.   _rdfGet_opType: function(item, property) {
  6762.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6763.     var key = this.getItemProperty(id, "installLocation");
  6764.     if (key in StartupCache.entries && id in StartupCache.entries[key] &&
  6765.         StartupCache.entries[key][id])
  6766.       return EM_L(StartupCache.entries[key][id].op);
  6767.     return EM_L(OP_NONE);
  6768.   },
  6769.  
  6770.   /**
  6771.    * Gets a localizable property. Install Manifests are generally only in one 
  6772.    * language, however an item can customize by providing localized prefs in 
  6773.    * the form:
  6774.    *
  6775.    *    extensions.{GUID}.[name|description|creator|homepageURL]
  6776.    *
  6777.    * to specify localized text for each of these properties.
  6778.    */
  6779.   _getLocalizablePropertyValue: function(item, property) {
  6780.     // These are localizable properties that a language pack supplied by the 
  6781.     // Extension may override.          
  6782.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6783.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6784.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6785.     try {
  6786.       var value = gPref.getComplexValue(prefName, 
  6787.                                         Components.interfaces.nsIPrefLocalizedString);
  6788.       if (value.data) 
  6789.         return EM_L(value.data);
  6790.     }
  6791.     catch (e) {
  6792.     }
  6793.     return null;
  6794.   },
  6795.   
  6796.   /**
  6797.    * Get the em:name property (name of the item)
  6798.    */
  6799.   _rdfGet_name: function(item, property) {
  6800.     return this._getLocalizablePropertyValue(item, property);
  6801.   },
  6802.   
  6803.   /**
  6804.    * Get the em:description property (description of the item)
  6805.    */
  6806.   _rdfGet_description: function(item, property) {
  6807.     return this._getLocalizablePropertyValue(item, property);
  6808.   },
  6809.   
  6810.   /**
  6811.    * Get the em:creator property (creator of the item)
  6812.    */
  6813.   _rdfGet_creator: function(item, property) { 
  6814.     return this._getLocalizablePropertyValue(item, property);
  6815.   },
  6816.   
  6817.   /**
  6818.    * Get the em:homepageURL property (homepage URL of the item)
  6819.    */
  6820.   _rdfGet_homepageURL: function(item, property) {
  6821.     return this._getLocalizablePropertyValue(item, property);
  6822.   },
  6823.  
  6824.   /**
  6825.    * Get the em:disabled property. This will be true if the item has a
  6826.    * appDisabled or a userDisabled property that is true as long as it is not
  6827.    * about to be disabled.
  6828.    */
  6829.   _rdfGet_disabled: function(item, property) {
  6830.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6831.     if ((this.getItemProperty(id, "userDisabled") == "true" ||
  6832.         this.getItemProperty(id, "appDisabled") == "true") &&
  6833.         this.getItemProperty(id, "opType") != OP_NEEDS_DISABLE)
  6834.       return EM_L("true");
  6835.  
  6836.     // Migrate disabled in the extensions datasource to userDisabled or
  6837.     // appDisabled as appropriate.
  6838.     var oldDisabled = this._inner.GetTarget(item, property, true);
  6839.     if (oldDisabled instanceof Components.interfaces.nsIRDFLiteral) {
  6840.       this._inner.Unassert(item, property, oldDisabled);
  6841.       if (this.getItemProperty(id, "compatible") == "true")
  6842.         this.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  6843.       else
  6844.         this.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  6845.  
  6846.       return EM_L("true");
  6847.     }
  6848.  
  6849.     return EM_L("false");
  6850.   },
  6851.  
  6852.   /**
  6853.    * Get the em:updateable property - this specifies whether the item is
  6854.    * allowed to be updated
  6855.    */
  6856.   _rdfGet_updateable: function(item, property) {
  6857.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6858.     var opType = this.getItemProperty(id, "opType");
  6859.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  6860.         opType == OP_NEEDS_UPGRADE ||
  6861.         this.getItemProperty(id, "appManaged") == "true")
  6862.       return EM_L("false");
  6863.  
  6864.     try {
  6865.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id)))
  6866.         return EM_L("false");
  6867.     }
  6868.     catch (e) { }
  6869.  
  6870.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  6871.     if (!installLocation || !installLocation.canAccess)
  6872.       return EM_L("false");
  6873.  
  6874.     return EM_L("true");
  6875.   },
  6876.  
  6877.   /**
  6878.    * See nsIRDFDataSource.idl
  6879.    */
  6880.   GetTarget: function(source, property, truthValue) {
  6881.     if (!source)
  6882.       return null;
  6883.       
  6884.     var target = null;
  6885.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  6886.     if (getter in this)
  6887.       target = this[getter](source, property);
  6888.  
  6889.     return target || this._inner.GetTarget(source, property, truthValue);
  6890.   },
  6891.   
  6892.   /**
  6893.    * Gets an enumeration of values of a localizable property. Install Manifests
  6894.    * are generally only in one language, however an item can customize by 
  6895.    * providing localized prefs in the form:
  6896.    *
  6897.    *    extensions.{GUID}.[contributor].1
  6898.    *    extensions.{GUID}.[contributor].2
  6899.    *    extensions.{GUID}.[contributor].3
  6900.    *    ...
  6901.    *
  6902.    * to specify localized text for each of these properties.
  6903.    */
  6904.   _getLocalizablePropertyValues: function(item, property) {
  6905.     // These are localizable properties that a language pack supplied by the 
  6906.     // Extension may override.          
  6907.     var values = [];
  6908.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6909.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6910.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6911.     var i = 0;
  6912.     do {
  6913.       try {
  6914.         var value = gPref.getComplexValue(prefName + "." + ++i, 
  6915.                                           Components.interfaces.nsIPrefLocalizedString);
  6916.         if (value.data) 
  6917.           values.push(EM_L(value.data));
  6918.       }
  6919.       catch (e) {
  6920.         try {
  6921.           var value = gPref.getComplexValue(prefName, 
  6922.                                             Components.interfaces.nsIPrefLocalizedString);
  6923.           if (value.data) 
  6924.             values.push(EM_L(value.data));
  6925.         }
  6926.         catch (e) {
  6927.         }
  6928.         break;
  6929.       }
  6930.     }
  6931.     while (1);
  6932.     return values;
  6933.   },
  6934.   
  6935.   /**
  6936.    * Get the em:name property (name of the item - template builder calls
  6937.    * GetTargets on this one for some reason).
  6938.    */
  6939.   _rdfGets_name: function(item, property) {
  6940.     return this._getLocalizablePropertyValues(item, property);
  6941.   },
  6942.   
  6943.   /**
  6944.    * Get the em:contributor property (contributors to the extension)
  6945.    */
  6946.   _rdfGets_contributor: function(item, property) {
  6947.     return this._getLocalizablePropertyValues(item, property); 
  6948.   },
  6949.   
  6950.   /**
  6951.    * See nsIRDFDataSource.idl
  6952.    */
  6953.   GetTargets: function(source, property, truthValue) {
  6954.     if (!source)
  6955.       return null;
  6956.       
  6957.     var ary = null;
  6958.     var getter = "_rdfGets_" + stripPrefix(source.Value, EM_NS);
  6959.     if (getter in this)
  6960.       ary = this[getter](source, property);
  6961.     
  6962.     return ary ? new ArrayEnumerator(ary) 
  6963.                : this._inner.GetTargets(source, property, truthValue);
  6964.   },
  6965.   
  6966.   Assert: function(source, property, target, truthValue) {
  6967.     this._inner.Assert(source, property, target, truthValue);
  6968.   },
  6969.   
  6970.   Unassert: function(source, property, target) {
  6971.     this._inner.Unassert(source, property, target);
  6972.   },
  6973.   
  6974.   Change: function(source, property, oldTarget, newTarget) {
  6975.     this._inner.Change(source, property, oldTarget, newTarget);
  6976.   },
  6977.  
  6978.   Move: function(oldSource, newSource, property, target) {
  6979.     this._inner.Move(oldSource, newSource, property, target);
  6980.   },
  6981.   
  6982.   HasAssertion: function(source, property, target, truthValue) {
  6983.     if (!source || !property || !target)
  6984.       return false;
  6985.     return this._inner.HasAssertion(source, property, target, truthValue);
  6986.   },
  6987.   
  6988.   _observers: [],
  6989.   AddObserver: function(observer) {
  6990.     for (var i = 0; i < this._observers.length; ++i) {
  6991.       if (this._observers[i] == observer) 
  6992.         return;
  6993.     }
  6994.     this._observers.push(observer);
  6995.     this._inner.AddObserver(observer);
  6996.   },
  6997.   
  6998.   RemoveObserver: function(observer) {
  6999.     for (var i = 0; i < this._observers.length; ++i) {
  7000.       if (this._observers[i] == observer) 
  7001.         this._observers.splice(i, 1);
  7002.     }
  7003.     this._inner.RemoveObserver(observer);
  7004.   },
  7005.   
  7006.   ArcLabelsIn: function(node) {
  7007.     return this._inner.ArcLabelsIn(node);
  7008.   },
  7009.   
  7010.   ArcLabelsOut: function(source) {
  7011.     return this._inner.ArcLabelsOut(source);
  7012.   },
  7013.   
  7014.   GetAllResources: function() {
  7015.     return this._inner.GetAllResources();
  7016.   },
  7017.   
  7018.   IsCommandEnabled: function(sources, command, arguments) {
  7019.     return this._inner.IsCommandEnabled(sources, command, arguments);
  7020.   },
  7021.   
  7022.   DoCommand: function(sources, command, arguments) {
  7023.     this._inner.DoCommand(sources, command, arguments);
  7024.   },
  7025.   
  7026.   GetAllCmds: function(source) {
  7027.     return this._inner.GetAllCmds(source);
  7028.   },
  7029.   
  7030.   hasArcIn: function(node, arc) {
  7031.     return this._inner.hasArcIn(node, arc);
  7032.   },
  7033.   
  7034.   hasArcOut: function(source, arc) {
  7035.     return this._inner.hasArcOut(source, arc);
  7036.   },
  7037.   
  7038.   beginUpdateBatch: function() {
  7039.     return this._inner.beginUpdateBatch();
  7040.   },
  7041.   
  7042.   endUpdateBatch: function() {
  7043.     return this._inner.endUpdateBatch();
  7044.   },
  7045.   
  7046.   /**
  7047.    * See nsIRDFRemoteDataSource.idl
  7048.    */
  7049.   get loaded() {
  7050.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  7051.   },
  7052.   
  7053.   Init: function(uri) {
  7054.   },
  7055.   
  7056.   Refresh: function(blocking) {
  7057.   },
  7058.   
  7059.   Flush: function() {
  7060.     if (this._inner instanceof Components.interfaces.nsIRDFRemoteDataSource)
  7061.       this._inner.Flush();
  7062.   },
  7063.   
  7064.   FlushTo: function(uri) {
  7065.   },
  7066.   
  7067.   /**
  7068.    * See nsISupports.idl
  7069.    */
  7070.   QueryInterface: function(iid) {
  7071.     if (!iid.equals(Components.interfaces.nsIRDFDataSource) &&
  7072.         !iid.equals(Components.interfaces.nsIRDFRemoteDataSource) && 
  7073.         !iid.equals(Components.interfaces.nsISupports))
  7074.       throw Components.results.NS_ERROR_NO_INTERFACE;
  7075.     return this;
  7076.   }
  7077. };
  7078.  
  7079. function UpdateItem () {
  7080. }
  7081. UpdateItem.prototype = {
  7082.   /**
  7083.    * See nsIUpdateService.idl
  7084.    */
  7085.   init: function(id, version, installLocationKey, minAppVersion, maxAppVersion,
  7086.                  name, downloadURL, xpiHash, iconURL, updateURL, type) {
  7087.     this._id                  = id;
  7088.     this._version             = version;
  7089.     this._installLocationKey  = installLocationKey;
  7090.     this._minAppVersion       = minAppVersion;
  7091.     this._maxAppVersion       = maxAppVersion;
  7092.     this._name                = name;
  7093.     this._downloadURL         = downloadURL;
  7094.     this._xpiHash             = xpiHash;
  7095.     this._iconURL             = iconURL;
  7096.     this._updateURL           = updateURL;
  7097.     this._type                = type;
  7098.   },
  7099.   
  7100.   /**
  7101.    * See nsIUpdateService.idl
  7102.    */
  7103.   get id()                { return this._id;                },
  7104.   get version()           { return this._version;           },
  7105.   get installLocationKey(){ return this._installLocationKey;},
  7106.   get minAppVersion()     { return this._minAppVersion;     },
  7107.   get maxAppVersion()     { return this._maxAppVersion;     },
  7108.   get name()              { return this._name;              },
  7109.   get xpiURL()            { return this._downloadURL;       },
  7110.   get xpiHash()           { return this._xpiHash;           },
  7111.   get iconURL()           { return this._iconURL            },
  7112.   get updateRDF()         { return this._updateURL;         },
  7113.   get type()              { return this._type;              },
  7114.  
  7115.   /**
  7116.    * See nsIUpdateService.idl
  7117.    */
  7118.   get objectSource() {
  7119.     return { id                 : this._id, 
  7120.              version            : this._version, 
  7121.              installLocationKey : this._installLocationKey,
  7122.              minAppVersion      : this._minAppVersion,
  7123.              maxAppVersion      : this._maxAppVersion,
  7124.              name               : this._name, 
  7125.              xpiURL             : this._downloadURL, 
  7126.              xpiHash            : this._xpiHash,
  7127.              iconURL            : this._iconURL, 
  7128.              updateRDF          : this._updateURL,
  7129.              type               : this._type 
  7130.            }.toSource();
  7131.   },
  7132.   
  7133.   /**
  7134.    * See nsISupports.idl
  7135.    */
  7136.   QueryInterface: function(iid) {
  7137.     if (!iid.equals(Components.interfaces.nsIUpdateItem) &&
  7138.         !iid.equals(Components.interfaces.nsISupports))
  7139.       throw Components.results.NS_ERROR_NO_INTERFACE;
  7140.     return this;
  7141.   }
  7142. };
  7143.  
  7144. var gModule = {
  7145.   registerSelf: function(componentManager, fileSpec, location, type) {
  7146.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  7147.     
  7148.     for (var key in this._objects) {
  7149.       var obj = this._objects[key];
  7150.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  7151.                                                fileSpec, location, type);
  7152.     }
  7153.  
  7154.     // Make the Extension Manager a startup observer
  7155.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  7156.                                     .getService(Components.interfaces.nsICategoryManager);
  7157.     categoryManager.addCategoryEntry("app-startup", this._objects.manager.className,
  7158.                                      "service," + this._objects.manager.contractID, 
  7159.                                      true, true, null);
  7160.   },
  7161.   
  7162.   getClassObject: function(componentManager, cid, iid) {
  7163.     if (!iid.equals(Components.interfaces.nsIFactory))
  7164.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  7165.  
  7166.     for (var key in this._objects) {
  7167.       if (cid.equals(this._objects[key].CID))
  7168.         return this._objects[key].factory;
  7169.     }
  7170.     
  7171.     throw Components.results.NS_ERROR_NO_INTERFACE;
  7172.   },
  7173.   
  7174.   _makeFactory: #1= function(ctor) {
  7175.     return { 
  7176.              createInstance: function (outer, iid) {
  7177.                if (outer != null)
  7178.                  throw Components.results.NS_ERROR_NO_AGGREGATION;
  7179.                return (new ctor()).QueryInterface(iid);
  7180.              } 
  7181.            };  
  7182.   },
  7183.   
  7184.   _objects: {
  7185.     manager: { CID        : ExtensionManager.prototype.classID,
  7186.                contractID : ExtensionManager.prototype.contractID,
  7187.                className  : ExtensionManager.prototype.classDescription,
  7188.                factory    : #1#(ExtensionManager)
  7189.              },
  7190.     item:    { CID        : Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"),
  7191.                contractID : "@mozilla.org/updates/item;1",
  7192.                className  : "Update Item",
  7193.                factory    : #1#(UpdateItem)
  7194.              }
  7195.    },
  7196.  
  7197.   canUnload: function(componentManager) {
  7198.     return true;
  7199.   }
  7200. };
  7201.  
  7202. function NSGetModule(compMgr, fileSpec) {
  7203.   return gModule;
  7204. }
  7205.  
  7206.